mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-09-04 17:23:25 +00:00
FIX - stabilize FlipTok media interactions
Complete the FlipTok UI, store, mock API, and server audit. Add reliable photo-slide publishing, comments, privacy checks, action error handling, deletion, localization, and profile interaction states. Replace the photo carousel scroll path with a GPU-accelerated drag and swipe track that supports rapid consecutive gestures, and cover the corrected behavior with focused contract, store, and mock smoke tests.
This commit is contained in:
@@ -233,4 +233,30 @@ describe('FlipTok verification updates', () => {
|
||||
profileId: profile.id,
|
||||
})
|
||||
})
|
||||
|
||||
it('does not show a follow state when the server rejects it', async () => {
|
||||
vi.mocked(nuiCall).mockResolvedValue({ success: false })
|
||||
const store = useFlipTokStore()
|
||||
const creatorVideo = { ...video, is_owner: false, profile_id: 8 }
|
||||
store.feed = [creatorVideo]
|
||||
|
||||
expect(await store.follow(creatorVideo)).toBe(false)
|
||||
expect(creatorVideo.is_following).toBe(false)
|
||||
})
|
||||
|
||||
it('removes an owned video from every local surface after deletion', async () => {
|
||||
vi.mocked(nuiCall).mockResolvedValue({ success: true })
|
||||
const store = useFlipTokStore()
|
||||
store.profile = { ...profile, video_count: 1 }
|
||||
store.feed = [{ ...video }]
|
||||
store.searchResults = [{ ...video }]
|
||||
store.profileVideos = [{ ...video }]
|
||||
|
||||
expect(await store.deleteVideo(video.id)).toBe(true)
|
||||
expect(nuiCall).toHaveBeenCalledWith('fliptok:delete', { id: video.id })
|
||||
expect(store.feed).toEqual([])
|
||||
expect(store.searchResults).toEqual([])
|
||||
expect(store.profileVideos).toEqual([])
|
||||
expect(store.profile.video_count).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -137,26 +137,27 @@ export const useFlipTokStore = defineStore('fliptok', {
|
||||
if (kind === 'like') video.like_count += next ? -1 : 1
|
||||
}
|
||||
},
|
||||
async follow(video: FlipTokVideo): Promise<void> {
|
||||
async follow(video: FlipTokVideo): Promise<boolean> {
|
||||
const next = !video.is_following
|
||||
const response = await nuiCall('fliptok:follow', {
|
||||
active: next,
|
||||
profileId: video.profile_id,
|
||||
})
|
||||
if (response.success)
|
||||
this.feed
|
||||
.filter((item) => item.profile_id === video.profile_id)
|
||||
.forEach((item) => {
|
||||
item.is_following = next
|
||||
})
|
||||
if (!response.success) return false
|
||||
this.feed
|
||||
.filter((item) => item.profile_id === video.profile_id)
|
||||
.forEach((item) => {
|
||||
item.is_following = next
|
||||
})
|
||||
return true
|
||||
},
|
||||
async followProfile(profile: FlipTokProfile): Promise<void> {
|
||||
async followProfile(profile: FlipTokProfile): Promise<boolean> {
|
||||
const next = !profile.is_following
|
||||
const response = await nuiCall('fliptok:follow', {
|
||||
active: next,
|
||||
profileId: profile.id,
|
||||
})
|
||||
if (!response.success) return
|
||||
if (!response.success) return false
|
||||
profile.is_following = next
|
||||
profile.followers += next ? 1 : -1
|
||||
this.feed
|
||||
@@ -164,6 +165,7 @@ export const useFlipTokStore = defineStore('fliptok', {
|
||||
.forEach((item) => {
|
||||
item.is_following = next
|
||||
})
|
||||
return true
|
||||
},
|
||||
async loadProfile(query: {
|
||||
handle?: string
|
||||
@@ -201,11 +203,12 @@ export const useFlipTokStore = defineStore('fliptok', {
|
||||
if (this.viewedProfile?.id === profileId) this.viewedProfile = null
|
||||
return true
|
||||
},
|
||||
async loadComments(id: string): Promise<void> {
|
||||
async loadComments(id: string): Promise<boolean> {
|
||||
const response = await nuiCall<FlipTokComment[]>('fliptok:comments', {
|
||||
id,
|
||||
})
|
||||
this.comments = response.success && response.data ? response.data : []
|
||||
return response.success
|
||||
},
|
||||
async comment(
|
||||
id: string,
|
||||
@@ -237,10 +240,23 @@ export const useFlipTokStore = defineStore('fliptok', {
|
||||
this.connections = response.success && response.data ? response.data : []
|
||||
return response.success
|
||||
},
|
||||
async loadActivities(): Promise<void> {
|
||||
async loadActivities(): Promise<boolean> {
|
||||
const response = await nuiCall<FlipTokActivity[]>('fliptok:activities')
|
||||
this.activities = response.success && response.data ? response.data : []
|
||||
if (response.success) await nuiCall('fliptok:mark-activities')
|
||||
return response.success
|
||||
},
|
||||
async deleteVideo(id: string): Promise<boolean> {
|
||||
const response = await nuiCall('fliptok:delete', { id })
|
||||
if (!response.success) return false
|
||||
this.feed = this.feed.filter((video) => video.id !== id)
|
||||
this.searchResults = this.searchResults.filter((video) => video.id !== id)
|
||||
this.profileVideos = this.profileVideos.filter((video) => video.id !== id)
|
||||
if (this.profile && this.profile.video_count > 0)
|
||||
this.profile.video_count -= 1
|
||||
if (this.viewedProfile?.is_owner && this.viewedProfile.video_count > 0)
|
||||
this.viewedProfile.video_count -= 1
|
||||
return true
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1467,18 +1467,28 @@ const defaultLocales: LocaleTree = {
|
||||
emptyFeedBody: 'Follow creators or post the first FlipTok.',
|
||||
searchPlaceholder: 'Search creators and videos',
|
||||
clearSearch: 'Clear search',
|
||||
trendLosSantos: '# Los Santos',
|
||||
trendRoleplay: '# Roleplay',
|
||||
trendTrending: '# Trending',
|
||||
noActivity: 'No activity yet',
|
||||
followers: 'Followers',
|
||||
videos: 'Videos',
|
||||
emptyBio: 'No bio yet.',
|
||||
editProfile: 'Edit profile',
|
||||
more: 'More',
|
||||
newVideo: 'New FlipTok',
|
||||
createTitle: 'Create your next FlipTok',
|
||||
createBody:
|
||||
'Choose a clip, set the cover and sound, then decide who can watch it.',
|
||||
chooseVideo: 'Choose a video',
|
||||
chooseVideoHint: 'Select one from Photos',
|
||||
changeVideo: 'Change',
|
||||
createBody: 'Record a clip or turn photos into a swipeable FlipTok.',
|
||||
chooseMedia: 'Choose media',
|
||||
chooseMediaHint:
|
||||
'Record a video, choose a clip, or select up to 10 photos.',
|
||||
recordVideo: 'Record video',
|
||||
camera: 'Camera',
|
||||
chooseVideo: 'Choose video',
|
||||
photoSlideshow: 'Photos as FlipTok',
|
||||
changeMedia: 'Change',
|
||||
previousPhoto: 'Previous photo',
|
||||
nextPhoto: 'Next photo',
|
||||
caption: 'Caption',
|
||||
captionPlaceholder: 'Write a caption...',
|
||||
location: 'Add location',
|
||||
@@ -1492,6 +1502,11 @@ const defaultLocales: LocaleTree = {
|
||||
post: 'Post',
|
||||
draftSaved: 'Draft saved.',
|
||||
published: 'Your FlipTok is live.',
|
||||
videoDeleted: 'Video removed.',
|
||||
deleteVideoTitle: 'Remove this video?',
|
||||
deleteVideoBody:
|
||||
'The video disappears from FlipTok and cannot be restored.',
|
||||
deletingVideo: 'Removing...',
|
||||
linkCopied: 'Video link copied.',
|
||||
reported: 'Report submitted.',
|
||||
blocked: 'Creator blocked.',
|
||||
@@ -1499,6 +1514,8 @@ const defaultLocales: LocaleTree = {
|
||||
noComments: 'No comments yet',
|
||||
addComment: 'Add comment...',
|
||||
reply: 'Reply',
|
||||
showReplies: 'Show {count} replies',
|
||||
hideReplies: 'Hide {count} replies',
|
||||
replyPlaceholder: 'Write a reply...',
|
||||
replyingTo: 'Replying to {handle}',
|
||||
cancelReply: 'Cancel reply',
|
||||
@@ -1506,6 +1523,8 @@ const defaultLocales: LocaleTree = {
|
||||
reportReason: 'Reason',
|
||||
reportDetails: 'Additional details (optional)',
|
||||
submitReport: 'Submit report',
|
||||
reportDiscordNote:
|
||||
'This report is sent directly to the moderation team through Discord.',
|
||||
reportReasons: {
|
||||
spam: 'Spam or misleading',
|
||||
harassment: 'Harassment or bullying',
|
||||
@@ -1547,6 +1566,7 @@ const defaultLocales: LocaleTree = {
|
||||
dismissReport: 'Dismiss',
|
||||
cancel: 'Cancel',
|
||||
done: 'Done',
|
||||
savingProfile: 'Saving...',
|
||||
displayName: 'Name',
|
||||
username: 'Username',
|
||||
bio: 'Bio',
|
||||
@@ -1605,7 +1625,7 @@ const defaultLocales: LocaleTree = {
|
||||
invalid_video: 'Check the video details.',
|
||||
invalid_music_url:
|
||||
'Use a valid YouTube or direct HTTPS audio-file link.',
|
||||
invalid_media: 'Choose a video from this phone.',
|
||||
invalid_media: 'Choose valid media from this phone.',
|
||||
invalid_comment: 'Enter a valid comment.',
|
||||
comments_disabled: 'Comments are disabled.',
|
||||
invalid_profile: 'Check your profile details.',
|
||||
@@ -1618,11 +1638,15 @@ const defaultLocales: LocaleTree = {
|
||||
'This Sky Cloud account already owns a registered FlipTok profile.',
|
||||
handle_taken: 'This username is already taken.',
|
||||
video_not_found: 'This video is unavailable.',
|
||||
profile_not_found: 'This profile is unavailable.',
|
||||
rate_limited: 'Too many actions. Try again shortly.',
|
||||
not_authenticated: 'Sign in to Sky Cloud first.',
|
||||
blocked: 'This account is blocked.',
|
||||
not_authorized: 'You do not have moderation access.',
|
||||
report_not_found: 'This report is no longer open.',
|
||||
report_unavailable: 'Discord reporting is not configured yet.',
|
||||
request_failed:
|
||||
'Your FlipTok request could not be completed. Try again.',
|
||||
default: 'FlipTok could not complete the request.',
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user