mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-29 01:08:59 +00:00
FIX - polish FlipTok profile interactions
Improve feed follow feedback, authentication titles, connection avatar alignment, and following-list state. Keep already-followed profiles disabled and cover the behavior with store and UI contract tests.
This commit is contained in:
@@ -234,6 +234,24 @@ describe('FlipTok verification updates', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('marks every account in the own following list as already followed', async () => {
|
||||
const followedProfile = {
|
||||
...profile,
|
||||
id: 8,
|
||||
is_following: false,
|
||||
is_owner: false,
|
||||
}
|
||||
vi.mocked(nuiCall).mockResolvedValue({
|
||||
success: true,
|
||||
data: [followedProfile],
|
||||
})
|
||||
const store = useFlipTokStore()
|
||||
store.profile = { ...profile }
|
||||
|
||||
expect(await store.loadConnections(profile.id, 'following')).toBe(true)
|
||||
expect(store.connections[0]?.is_following).toBe(true)
|
||||
})
|
||||
|
||||
it('does not show a follow state when the server rejects it', async () => {
|
||||
vi.mocked(nuiCall).mockResolvedValue({ success: false })
|
||||
const store = useFlipTokStore()
|
||||
|
||||
@@ -238,6 +238,11 @@ export const useFlipTokStore = defineStore('fliptok', {
|
||||
profileId,
|
||||
})
|
||||
this.connections = response.success && response.data ? response.data : []
|
||||
if (mode === 'following' && profileId === this.profile?.id) {
|
||||
this.connections.forEach((profile) => {
|
||||
if (!profile.is_owner) profile.is_following = true
|
||||
})
|
||||
}
|
||||
return response.success
|
||||
},
|
||||
async loadActivities(): Promise<boolean> {
|
||||
|
||||
@@ -98,11 +98,15 @@ describe('FlipTokApp Sky UI contract', () => {
|
||||
expect(source).toContain("route.query.compose === '1'")
|
||||
expect(source).toContain('mediaIds,')
|
||||
expect(source).toContain('class="photo-slideshow"')
|
||||
expect(source).toContain('@pointerdown="beginPhotoSlideDrag(video.id, $event)"')
|
||||
expect(source).toContain(
|
||||
'@pointerdown="beginPhotoSlideDrag(video.id, $event)"',
|
||||
)
|
||||
expect(source).toContain('@pointermove="updatePhotoSlideDrag"')
|
||||
expect(source).toContain('@pointerup="endPhotoSlideDrag(video, $event)"')
|
||||
expect(source).not.toContain('photo-slideshow__arrow')
|
||||
expect(source).toContain('window.requestAnimationFrame(() => renderPhotoSlideDrag(drag))')
|
||||
expect(source).toContain(
|
||||
'window.requestAnimationFrame(() => renderPhotoSlideDrag(drag))',
|
||||
)
|
||||
expect(source).toContain('function settlePhotoSlide(')
|
||||
expect(source).toContain('class="photo-slideshow__track"')
|
||||
expect(source).toContain('const animation = track.animate(')
|
||||
@@ -129,7 +133,10 @@ describe('FlipTokApp Sky UI contract', () => {
|
||||
it('shows a centered transient follow control', () => {
|
||||
expect(source).toContain('followFeedbackIds')
|
||||
expect(source).toContain('follow-dot--confirmed')
|
||||
expect(source).toContain('@click="followFromFeed(video)"')
|
||||
expect(source).toContain('@click.stop="followFromFeed(video)"')
|
||||
expect(source).toContain(
|
||||
'video.is_following || followPendingIds.has(video.id)',
|
||||
)
|
||||
expect(source).toMatch(
|
||||
/\.video-actions \.follow-dot\s*\{[^}]*min-width: 20px !important;[^}]*min-height: 20px !important;[^}]*place-items: center;/s,
|
||||
)
|
||||
@@ -150,10 +157,21 @@ describe('FlipTokApp Sky UI contract', () => {
|
||||
expect(source).toMatch(
|
||||
/\.profile-navbar-actions :deep\(\.sky-link\)\s*\{[^}]*width: 38px;[^}]*border:/s,
|
||||
)
|
||||
expect(source).toContain(':disabled="profile.is_following"')
|
||||
expect(source).toContain(':disabled="connectionIsFollowing(profile)"')
|
||||
expect(source).toContain("connectionsMode.value === 'following'")
|
||||
expect(source).toContain('@click="followConnection(profile)"')
|
||||
})
|
||||
|
||||
it('labels authentication modes and centers connection avatar fallbacks', () => {
|
||||
expect(source).toContain(
|
||||
":title=\"t(authMode === 'login' ? 'login' : 'register')\"",
|
||||
)
|
||||
expect(source).toContain('class="connection-avatar__fallback"')
|
||||
expect(source).toMatch(
|
||||
/\.connections-list \.connection-avatar__fallback\s*\{[^}]*display:\s*grid;[^}]*place-items:\s*center;/s,
|
||||
)
|
||||
})
|
||||
|
||||
it('supports validated custom audio links in the composer', () => {
|
||||
expect(source).toContain('function validCustomMusicUrl')
|
||||
expect(source).toContain('customMusicDraftUrl')
|
||||
|
||||
@@ -361,9 +361,7 @@ function beginPhotoSlideDrag(videoId: string, event: PointerEvent): void {
|
||||
element.setPointerCapture(event.pointerId)
|
||||
}
|
||||
|
||||
function renderPhotoSlideDrag(
|
||||
drag: NonNullable<typeof photoSlideDrag>,
|
||||
): void {
|
||||
function renderPhotoSlideDrag(drag: NonNullable<typeof photoSlideDrag>): void {
|
||||
drag.frame = null
|
||||
if (photoSlideDrag !== drag) return
|
||||
const translateX = drag.startTranslateX + (drag.currentX - drag.startX)
|
||||
@@ -409,7 +407,8 @@ function endPhotoSlideDrag(video: FlipTokVideo, event: PointerEvent): void {
|
||||
shouldAdvance ? drag.startIndex + direction : drag.startIndex,
|
||||
),
|
||||
)
|
||||
if (element.hasPointerCapture(pointerId)) element.releasePointerCapture(pointerId)
|
||||
if (element.hasPointerCapture(pointerId))
|
||||
element.releasePointerCapture(pointerId)
|
||||
element.classList.remove('photo-slideshow--dragging')
|
||||
photoSlideDrag = null
|
||||
settlePhotoSlide(video.id, element, nextIndex)
|
||||
@@ -437,7 +436,10 @@ function settlePhotoSlide(
|
||||
element.classList.add('photo-slideshow--settling')
|
||||
const animation = track.animate(
|
||||
[
|
||||
{ transform: startTransform === 'none' ? track.style.transform : startTransform },
|
||||
{
|
||||
transform:
|
||||
startTransform === 'none' ? track.style.transform : startTransform,
|
||||
},
|
||||
{ transform: animationTarget },
|
||||
],
|
||||
{
|
||||
@@ -1401,10 +1403,18 @@ async function openConnectionProfile(profile: FlipTokProfile): Promise<void> {
|
||||
}
|
||||
|
||||
async function followConnection(profile: FlipTokProfile): Promise<void> {
|
||||
if (profile.is_owner || profile.is_following) return
|
||||
if (profile.is_owner || connectionIsFollowing(profile)) return
|
||||
if (!(await store.followProfile(profile))) notify(t('errors.default'))
|
||||
}
|
||||
|
||||
function connectionIsFollowing(profile: FlipTokProfile): boolean {
|
||||
return (
|
||||
profile.is_following ||
|
||||
(connectionsMode.value === 'following' &&
|
||||
currentProfile.value?.is_owner === true)
|
||||
)
|
||||
}
|
||||
|
||||
function openActions(video: FlipTokVideo): void {
|
||||
selectedVideo.value = video
|
||||
actionsOpen.value = true
|
||||
@@ -1767,7 +1777,7 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<SkyNavbar
|
||||
class="fliptok-auth__navbar"
|
||||
:title="t('name')"
|
||||
:title="t(authMode === 'login' ? 'login' : 'register')"
|
||||
:scroll-el="null"
|
||||
variant="medium"
|
||||
/>
|
||||
@@ -2037,13 +2047,15 @@ onBeforeUnmount(() => {
|
||||
!video.is_owner &&
|
||||
(!video.is_following || followFeedbackIds.has(video.id))
|
||||
"
|
||||
type="button"
|
||||
class="follow-dot"
|
||||
:class="{
|
||||
'follow-dot--confirmed': video.is_following,
|
||||
'follow-dot--pending': followPendingIds.has(video.id),
|
||||
}"
|
||||
:disabled="followPendingIds.has(video.id)"
|
||||
@click="followFromFeed(video)"
|
||||
:aria-label="video.is_following ? t('unfollow') : t('follow')"
|
||||
:disabled="video.is_following || followPendingIds.has(video.id)"
|
||||
@click.stop="followFromFeed(video)"
|
||||
>
|
||||
<Check v-if="video.is_following" /><Plus v-else />
|
||||
</button>
|
||||
@@ -3277,9 +3289,9 @@ onBeforeUnmount(() => {
|
||||
:src="profile.avatar_url"
|
||||
alt=""
|
||||
/>
|
||||
<template v-else>{{
|
||||
initials(profile.display_name)
|
||||
}}</template>
|
||||
<span v-else class="connection-avatar__fallback">
|
||||
{{ initials(profile.display_name) }}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
@@ -3288,11 +3300,11 @@ onBeforeUnmount(() => {
|
||||
v-if="!profile.is_owner"
|
||||
small
|
||||
rounded
|
||||
:tonal="profile.is_following"
|
||||
:disabled="profile.is_following"
|
||||
:tonal="connectionIsFollowing(profile)"
|
||||
:disabled="connectionIsFollowing(profile)"
|
||||
@click="followConnection(profile)"
|
||||
>{{
|
||||
profile.is_following ? t('unfollow') : t('follow')
|
||||
connectionIsFollowing(profile) ? t('unfollow') : t('follow')
|
||||
}}</SkyButton
|
||||
>
|
||||
</template>
|
||||
@@ -5905,12 +5917,18 @@ onBeforeUnmount(() => {
|
||||
border: 2px solid #111;
|
||||
border-radius: 50% !important;
|
||||
background: var(--sky-app-accent) !important;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.video-actions .follow-dot svg {
|
||||
width: 11px !important;
|
||||
height: 11px !important;
|
||||
margin: 0;
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.video-actions .follow-dot:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* No unused action row is reserved above Discover. */
|
||||
@@ -6046,10 +6064,21 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.connections-list .connection-avatar {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.connections-list .connection-avatar__fallback {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
line-height: 1;
|
||||
transform: translateY(-0.5px);
|
||||
}
|
||||
|
||||
.connections-list :deep(.sky-button:disabled) {
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user