From 16e002546e12c5cfc34c0aff1d2ee826d724a198 Mon Sep 17 00:00:00 2001 From: hassnian Date: Sat, 8 Jun 2024 12:38:01 +0500 Subject: [PATCH 01/14] add(profiles): allow clearing profile banner and image --- components/profile/create/Modal.vue | 2 +- components/profile/create/SelectImageField.vue | 10 ++++++++-- components/profile/create/stages/Form.vue | 8 ++++++-- services/profile.ts | 4 ++-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/components/profile/create/Modal.vue b/components/profile/create/Modal.vue index 0d94614989..acc135af59 100644 --- a/components/profile/create/Modal.vue +++ b/components/profile/create/Modal.vue @@ -109,7 +109,7 @@ const processProfile = async (profileData: ProfileFormData) => { name: profileData.name, description: profileData.description, image: imageUrl, - banner: bannerUrl, + banner: hasProfile.value ? bannerUrl ?? null : bannerUrl!, socials: constructSocials(profileData), } diff --git a/components/profile/create/SelectImageField.vue b/components/profile/create/SelectImageField.vue index 6bc107aaa4..0c84b3c2a5 100644 --- a/components/profile/create/SelectImageField.vue +++ b/components/profile/create/SelectImageField.vue @@ -21,12 +21,12 @@ vSelectedFile?.name ?? 'Click To Select A File' }} + @click="clear" /> @@ -35,6 +35,7 @@ import { NeoButton, NeoIcon, NeoUpload } from '@kodadot1/brick' const NuxtImg = resolveComponent('NuxtImg') +const emits = defineEmits(['clear']) const props = defineProps<{ modelValue: File | null preview?: string @@ -46,6 +47,11 @@ const selectedFilePreview = computed(() => vSelectedFile.value ? URL.createObjectURL(vSelectedFile.value as File) : '', ) +const clear = () => { + vSelectedFile.value = null + emits('clear') +} + const fileSelected = (file: File | null) => { vSelectedFile.value = file } diff --git a/components/profile/create/stages/Form.vue b/components/profile/create/stages/Form.vue index 7f9008464a..61cced8ee8 100644 --- a/components/profile/create/stages/Form.vue +++ b/components/profile/create/stages/Form.vue @@ -45,7 +45,10 @@

Recommended: 400x400px, up to 2MB (JPG, PNG)

- + @@ -60,7 +63,8 @@

+ :preview="form.bannerPreview" + @clear="form.bannerPreview = undefined" /> diff --git a/services/profile.ts b/services/profile.ts index b8bbf69037..d489741b4f 100644 --- a/services/profile.ts +++ b/services/profile.ts @@ -44,7 +44,7 @@ export type CreateProfileRequest = { name: string description: string image: string - banner: string + banner: string | undefined socials: SocialLink[] } @@ -53,7 +53,7 @@ export type UpdateProfileRequest = { name?: string description?: string image?: string - banner?: string + banner: string | null socials: SocialLink[] } From 186818a881019bae342133104a1666643da7d709 Mon Sep 17 00:00:00 2001 From: hassnian Date: Sat, 8 Jun 2024 13:19:02 +0500 Subject: [PATCH 02/14] ref(SelectImageField.vue): rename emits to emit --- components/profile/create/SelectImageField.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/profile/create/SelectImageField.vue b/components/profile/create/SelectImageField.vue index 0c84b3c2a5..eab5bac8ff 100644 --- a/components/profile/create/SelectImageField.vue +++ b/components/profile/create/SelectImageField.vue @@ -35,7 +35,7 @@ import { NeoButton, NeoIcon, NeoUpload } from '@kodadot1/brick' const NuxtImg = resolveComponent('NuxtImg') -const emits = defineEmits(['clear']) +const emit = defineEmits(['clear']) const props = defineProps<{ modelValue: File | null preview?: string @@ -49,7 +49,7 @@ const selectedFilePreview = computed(() => const clear = () => { vSelectedFile.value = null - emits('clear') + emit('clear') } const fileSelected = (file: File | null) => { From 1628853a494f0de6c5bc1ab324fdae1e52bbd875 Mon Sep 17 00:00:00 2001 From: hassnian Date: Sat, 8 Jun 2024 14:11:41 +0500 Subject: [PATCH 03/14] add(profiles): delete profile feature --- components/profile/ProfileDetail.vue | 5 +- components/profile/create/Modal.vue | 17 ++- components/profile/create/stages/Form.vue | 140 +++++++++++++--------- locales/en.json | 4 +- services/profile.ts | 13 ++ 5 files changed, 118 insertions(+), 61 deletions(-) diff --git a/components/profile/ProfileDetail.vue b/components/profile/ProfileDetail.vue index e07591ef99..a4b2fe24ac 100644 --- a/components/profile/ProfileDetail.vue +++ b/components/profile/ProfileDetail.vue @@ -1,6 +1,9 @@ @@ -111,33 +131,69 @@ import { Profile } from '@/services/profile' import { addHttpToUrl } from '@/utils/url' import { StatusAPIResponse } from '@farcaster/auth-client' -const { accountId } = useAuth() +const FarcasterIcon = defineAsyncComponent( + () => import('@/assets/icons/farcaster-icon.svg?component'), +) +const socialLinks = [ + { + name: 'farcaster', + icon: FarcasterIcon, + model: 'farcasterHandle', + placeholder: 'Farcaster Handle', + testId: 'create-profile-input-farcaster-handle', + }, + { + name: 'website', + icon: () => h(NeoIcon, { icon: 'globe', pack: 'fas' }), + model: 'website', + placeholder: 'Website', + testId: 'create-profile-input-website', + }, + { + name: 'twitter', + icon: () => h(NeoIcon, { icon: 'twitter', pack: 'fab' }), + model: 'twitterHandle', + placeholder: 'Twitter Handle', + testId: 'create-profile-input-twitter-handle', + }, +] + +const emit = defineEmits<{ + (e: 'submit', value: ProfileFormData): void + (e: 'delete', address: string): void +}>() const props = defineProps<{ farcasterUserData?: StatusAPIResponse useFarcaster: boolean }>() +const deleteConfirm = ref(false) + +const { accountId } = useAuth() const profile = inject<{ userProfile: Ref; hasProfile: Ref }>( 'userProfile', ) -const userProfile = computed(() => profile?.userProfile.value) -const substrateAddress = computed(() => formatAddress(accountId.value, 42)) - -const FarcasterIcon = defineAsyncComponent( - () => import('@/assets/icons/farcaster-icon.svg?component'), -) +const substrateAddress = computed(() => formatAddress(accountId.value, 42)) +const form = reactive({ + address: substrateAddress.value, + name: '', + description: '', + image: null, + imagePreview: undefined, + banner: null, + bannerPreview: undefined, + farcasterHandle: undefined, + twitterHandle: undefined, + website: undefined, +}) +const userProfile = computed(() => profile?.userProfile.value) const missingImage = computed(() => (form.imagePreview ? false : !form.image)) - const submitDisabled = computed( () => !form.name || !form.description || missingImage.value, ) -const emit = defineEmits<{ - (e: 'submit', value: ProfileFormData): void -}>() - const validatingFormInput = (model: string) => { switch (model) { case 'farcasterHandle': @@ -153,43 +209,13 @@ const validatingFormInput = (model: string) => { } } -// form state -const form = reactive({ - address: substrateAddress.value, - name: '', - description: '', - image: null, - imagePreview: undefined, - banner: null, - bannerPreview: undefined, - farcasterHandle: undefined, - twitterHandle: undefined, - website: undefined, -}) - -const socialLinks = [ - { - name: 'farcaster', - icon: FarcasterIcon, - model: 'farcasterHandle', - placeholder: 'Farcaster Handle', - testId: 'create-profile-input-farcaster-handle', - }, - { - name: 'website', - icon: () => h(NeoIcon, { icon: 'globe', pack: 'fas' }), - model: 'website', - placeholder: 'Website', - testId: 'create-profile-input-website', - }, - { - name: 'twitter', - icon: () => h(NeoIcon, { icon: 'twitter', pack: 'fab' }), - model: 'twitterHandle', - placeholder: 'Twitter Handle', - testId: 'create-profile-input-twitter-handle', - }, -] +const deleteProfile = () => { + if (deleteConfirm.value) { + emit('delete', substrateAddress.value) + } else { + deleteConfirm.value = true + } +} watchEffect(async () => { const profile = userProfile.value diff --git a/locales/en.json b/locales/en.json index a9fd893690..04ad06ab0e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -2104,6 +2104,8 @@ "title": "Unsuccessful Connection", "message": "Somehting went wrong linking with your farcaster account, please try again." } - } + }, + "delete": "Reset all Fields - start over", + "deleteConfirm": "You sure? - click again" } } diff --git a/services/profile.ts b/services/profile.ts index d489741b4f..eec722d630 100644 --- a/services/profile.ts +++ b/services/profile.ts @@ -142,6 +142,19 @@ export const updateProfile = async (updates: UpdateProfileRequest) => { } } +export const deleteProfile = async (address: string) => { + try { + const response = await api(`/profiles/${address}`, { + method: 'DELETE', + }) + return response + } catch (error) { + throw new Error( + `[PROFILE::DELETE] ERROR: ${(error as FetchError)?.data?.error?.issues[0]?.message}`, + ) + } +} + export const follow = async (followRequest: FollowRequest) => { try { const response = await api('/follow', { From 414daf5d07be7773c28fc89a3977e4f85910f908 Mon Sep 17 00:00:00 2001 From: hassnian Date: Sat, 8 Jun 2024 16:51:35 +0500 Subject: [PATCH 04/14] add(profiles): upload profile image --- components/profile/create/Modal.vue | 34 +++++++++++++++++++------ services/profile.ts | 39 +++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/components/profile/create/Modal.vue b/components/profile/create/Modal.vue index 8c0e639f98..7febca4d02 100644 --- a/components/profile/create/Modal.vue +++ b/components/profile/create/Modal.vue @@ -34,13 +34,15 @@ import { } from './stages/index' import { CreateProfileRequest, + ProfileImageType, SocialLink, UpdateProfileRequest, createProfile, deleteProfile, + getObjectUrl, updateProfile, + uploadProfileImage, } from '@/services/profile' -import { rateLimitedPinFileToIPFS } from '@/services/nftStorage' import { appClient, createChannel } from '@/services/farcaster' import { StatusAPIResponse } from '@farcaster/auth-client' import { useDocumentVisibility } from '@vueuse/core' @@ -70,11 +72,19 @@ const close = () => { emit('close') } -const uploadImage = async ( - imageFile: File | null, -): Promise => - imageFile - ? sanitizeIpfsUrl(await rateLimitedPinFileToIPFS(imageFile)) +const uploadImage = async ({ + file, + type, + address, +}: { + file: File | null + type: ProfileImageType + address: string +}): Promise => + file + ? await uploadProfileImage({ file, address, type }).then((response) => + getObjectUrl(response.data?.key as string), + ) : undefined const constructSocials = (profileData: ProfileFormData): SocialLink[] => { @@ -99,11 +109,19 @@ const constructSocials = (profileData: ProfileFormData): SocialLink[] => { const processProfile = async (profileData: ProfileFormData) => { const imageUrl = profileData.image - ? await uploadImage(profileData.image) + ? await uploadImage({ + file: profileData.image, + address: profileData.address, + type: 'image', + }) : profileData.imagePreview const bannerUrl = profileData.banner - ? await uploadImage(profileData.banner) + ? await uploadImage({ + file: profileData.banner, + address: profileData.address, + type: 'banner', + }) : profileData.bannerPreview const profileBody: CreateProfileRequest | UpdateProfileRequest = { diff --git a/services/profile.ts b/services/profile.ts index eec722d630..f7c992d846 100644 --- a/services/profile.ts +++ b/services/profile.ts @@ -9,6 +9,12 @@ const api = $fetch.create({ baseURL: BASE_URL, }) +const PUBLIC_R2_BUCKET_URL = isProduction + ? 'https://playground-r2.koda.art' + : 'https://pub-adc77a8fecb9405b9573442870905a67.r2.dev' + +export const getObjectUrl = (key: string) => `${PUBLIC_R2_BUCKET_URL}/${key}` + // Types for API request and response objects export type Profile = { address: string @@ -32,10 +38,10 @@ export type SocialLink = { link: string } -export type ProfileResponse = { +export type ProfileResponse = { success: boolean message: string - data?: Profile + data?: T profileId?: string } @@ -57,6 +63,14 @@ export type UpdateProfileRequest = { socials: SocialLink[] } +export type ProfileImageType = 'image' | 'banner' + +export type UploadProfileImageRequest = { + address: string + file: File + type: ProfileImageType +} + export type FollowRequest = { initiatorAddress: string targetAddress: string @@ -155,6 +169,27 @@ export const deleteProfile = async (address: string) => { } } +export const uploadProfileImage = async ( + uploadProfileImage: UploadProfileImageRequest, +) => { + const form = new FormData() + form.append('file', uploadProfileImage.file) + form.append('type', uploadProfileImage.type) + + try { + const response = await api>( + `/profiles/${uploadProfileImage.address}/image`, + { + method: 'POST', + body: form, + }, + ) + return response + } catch (error) { + throw new Error(`[PROFILE::FOLLOW] ERROR: ${(error as FetchError).data}`) + } +} + export const follow = async (followRequest: FollowRequest) => { try { const response = await api('/follow', { From e068568c852f3933caae61d13adad4ba4984200d Mon Sep 17 00:00:00 2001 From: hassnian Date: Mon, 10 Jun 2024 10:43:41 +0500 Subject: [PATCH 05/14] fix(profiles): upload profile image response type --- components/profile/create/Modal.vue | 2 +- services/profile.ts | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/components/profile/create/Modal.vue b/components/profile/create/Modal.vue index 7febca4d02..89ff357549 100644 --- a/components/profile/create/Modal.vue +++ b/components/profile/create/Modal.vue @@ -83,7 +83,7 @@ const uploadImage = async ({ }): Promise => file ? await uploadProfileImage({ file, address, type }).then((response) => - getObjectUrl(response.data?.key as string), + getObjectUrl(response.key), ) : undefined diff --git a/services/profile.ts b/services/profile.ts index f7c992d846..2dac91ce9b 100644 --- a/services/profile.ts +++ b/services/profile.ts @@ -38,13 +38,16 @@ export type SocialLink = { link: string } -export type ProfileResponse = { +type ProfileBaseResponse = { success: boolean message: string - data?: T - profileId?: string } +export type ProfileResponse = { + data?: Profile + profileId?: string +} & ProfileBaseResponse + export type CreateProfileRequest = { address: string name: string @@ -71,6 +74,10 @@ export type UploadProfileImageRequest = { type: ProfileImageType } +export type UploadProfileImageResponse = { + key: string +} & ProfileBaseResponse + export type FollowRequest = { initiatorAddress: string targetAddress: string @@ -177,7 +184,7 @@ export const uploadProfileImage = async ( form.append('type', uploadProfileImage.type) try { - const response = await api>( + const response = await api( `/profiles/${uploadProfileImage.address}/image`, { method: 'POST', From 132a1a4f7c59f57ba0c146ca40a67e9ae9e62ac4 Mon Sep 17 00:00:00 2001 From: hassnian Date: Mon, 10 Jun 2024 12:15:32 +0500 Subject: [PATCH 06/14] ref(profiles): remove upload image type --- components/profile/create/Modal.vue | 7 +------ services/profile.ts | 4 ---- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/components/profile/create/Modal.vue b/components/profile/create/Modal.vue index 89ff357549..4c680e5ec5 100644 --- a/components/profile/create/Modal.vue +++ b/components/profile/create/Modal.vue @@ -34,7 +34,6 @@ import { } from './stages/index' import { CreateProfileRequest, - ProfileImageType, SocialLink, UpdateProfileRequest, createProfile, @@ -74,15 +73,13 @@ const close = () => { const uploadImage = async ({ file, - type, address, }: { file: File | null - type: ProfileImageType address: string }): Promise => file - ? await uploadProfileImage({ file, address, type }).then((response) => + ? await uploadProfileImage({ file, address }).then((response) => getObjectUrl(response.key), ) : undefined @@ -112,7 +109,6 @@ const processProfile = async (profileData: ProfileFormData) => { ? await uploadImage({ file: profileData.image, address: profileData.address, - type: 'image', }) : profileData.imagePreview @@ -120,7 +116,6 @@ const processProfile = async (profileData: ProfileFormData) => { ? await uploadImage({ file: profileData.banner, address: profileData.address, - type: 'banner', }) : profileData.bannerPreview diff --git a/services/profile.ts b/services/profile.ts index 2dac91ce9b..ce81cd715c 100644 --- a/services/profile.ts +++ b/services/profile.ts @@ -66,12 +66,9 @@ export type UpdateProfileRequest = { socials: SocialLink[] } -export type ProfileImageType = 'image' | 'banner' - export type UploadProfileImageRequest = { address: string file: File - type: ProfileImageType } export type UploadProfileImageResponse = { @@ -181,7 +178,6 @@ export const uploadProfileImage = async ( ) => { const form = new FormData() form.append('file', uploadProfileImage.file) - form.append('type', uploadProfileImage.type) try { const response = await api( From 17c9a656d9078a09ec1195505092b3cb0983b75b Mon Sep 17 00:00:00 2001 From: hassnian Date: Mon, 10 Jun 2024 12:17:12 +0500 Subject: [PATCH 07/14] ref(profiles): add safety delay when deleting profile --- components/profile/create/stages/Form.vue | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/components/profile/create/stages/Form.vue b/components/profile/create/stages/Form.vue index 5e97ff3fad..d2a1b7f029 100644 --- a/components/profile/create/stages/Form.vue +++ b/components/profile/create/stages/Form.vue @@ -105,6 +105,7 @@ v-if="userProfile" variant="text" no-shadow + :disabled="isDeleteConfirmDisabled" :class="[deleteConfirm ? '!text-k-red' : '!text-k-grey', 'capitalize']" @click="deleteProfile">
@@ -168,13 +169,19 @@ const props = defineProps<{ useFarcaster: boolean }>() -const deleteConfirm = ref(false) +const deleteConfirm = ref() +const now = useNow() const { accountId } = useAuth() const profile = inject<{ userProfile: Ref; hasProfile: Ref }>( 'userProfile', ) +const isDeleteConfirmDisabled = computed(() => + deleteConfirm.value + ? now.value.getTime() - deleteConfirm.value.getTime() < 3000 + : false, +) const substrateAddress = computed(() => formatAddress(accountId.value, 42)) const form = reactive({ address: substrateAddress.value, @@ -213,7 +220,7 @@ const deleteProfile = () => { if (deleteConfirm.value) { emit('delete', substrateAddress.value) } else { - deleteConfirm.value = true + deleteConfirm.value = new Date() } } From ce7ff6169f0613dbc7ae815fe4eb024f3708f6e8 Mon Sep 17 00:00:00 2001 From: hassnian Date: Mon, 10 Jun 2024 17:57:46 +0500 Subject: [PATCH 08/14] ref(profiles): use image worker to upload images --- components/profile/create/Modal.vue | 29 +++++--------------- services/imageWorker.ts | 18 +++++++++++++ services/profile.ts | 42 ++--------------------------- 3 files changed, 27 insertions(+), 62 deletions(-) diff --git a/components/profile/create/Modal.vue b/components/profile/create/Modal.vue index 4c680e5ec5..24dcc714dc 100644 --- a/components/profile/create/Modal.vue +++ b/components/profile/create/Modal.vue @@ -38,10 +38,9 @@ import { UpdateProfileRequest, createProfile, deleteProfile, - getObjectUrl, updateProfile, - uploadProfileImage, } from '@/services/profile' +import { uploadImage } from '@/services/imageWorker' import { appClient, createChannel } from '@/services/farcaster' import { StatusAPIResponse } from '@farcaster/auth-client' import { useDocumentVisibility } from '@vueuse/core' @@ -71,18 +70,10 @@ const close = () => { emit('close') } -const uploadImage = async ({ - file, - address, -}: { - file: File | null - address: string -}): Promise => - file - ? await uploadProfileImage({ file, address }).then((response) => - getObjectUrl(response.key), - ) - : undefined +const uploadProfileImage = async ( + file: File | null, +): Promise => + file ? await uploadImage(file).then((response) => response.url) : undefined const constructSocials = (profileData: ProfileFormData): SocialLink[] => { return [ @@ -106,17 +97,11 @@ const constructSocials = (profileData: ProfileFormData): SocialLink[] => { const processProfile = async (profileData: ProfileFormData) => { const imageUrl = profileData.image - ? await uploadImage({ - file: profileData.image, - address: profileData.address, - }) + ? await uploadProfileImage(profileData.image) : profileData.imagePreview const bannerUrl = profileData.banner - ? await uploadImage({ - file: profileData.banner, - address: profileData.address, - }) + ? await uploadProfileImage(profileData.banner) : profileData.bannerPreview const profileBody: CreateProfileRequest | UpdateProfileRequest = { diff --git a/services/imageWorker.ts b/services/imageWorker.ts index 266855e097..aba3e1161d 100644 --- a/services/imageWorker.ts +++ b/services/imageWorker.ts @@ -40,3 +40,21 @@ export async function getMetadata(url: string) { return metadata as unknown as NFTWithMetadata } + +export const uploadImage = async (file: File) => { + try { + const form = new FormData() + form.append('file', file) + + workerUrl.pathname = '/image/upload' + + const response = await $fetch<{ url: string }>(workerUrl.toString(), { + method: 'POST', + body: form, + }) + + return response + } catch (error) { + throw new Error(`[IMAGE::UPLOAD] ERROR: ${error?.data}`) + } +} diff --git a/services/profile.ts b/services/profile.ts index ce81cd715c..eec722d630 100644 --- a/services/profile.ts +++ b/services/profile.ts @@ -9,12 +9,6 @@ const api = $fetch.create({ baseURL: BASE_URL, }) -const PUBLIC_R2_BUCKET_URL = isProduction - ? 'https://playground-r2.koda.art' - : 'https://pub-adc77a8fecb9405b9573442870905a67.r2.dev' - -export const getObjectUrl = (key: string) => `${PUBLIC_R2_BUCKET_URL}/${key}` - // Types for API request and response objects export type Profile = { address: string @@ -38,15 +32,12 @@ export type SocialLink = { link: string } -type ProfileBaseResponse = { +export type ProfileResponse = { success: boolean message: string -} - -export type ProfileResponse = { data?: Profile profileId?: string -} & ProfileBaseResponse +} export type CreateProfileRequest = { address: string @@ -66,15 +57,6 @@ export type UpdateProfileRequest = { socials: SocialLink[] } -export type UploadProfileImageRequest = { - address: string - file: File -} - -export type UploadProfileImageResponse = { - key: string -} & ProfileBaseResponse - export type FollowRequest = { initiatorAddress: string targetAddress: string @@ -173,26 +155,6 @@ export const deleteProfile = async (address: string) => { } } -export const uploadProfileImage = async ( - uploadProfileImage: UploadProfileImageRequest, -) => { - const form = new FormData() - form.append('file', uploadProfileImage.file) - - try { - const response = await api( - `/profiles/${uploadProfileImage.address}/image`, - { - method: 'POST', - body: form, - }, - ) - return response - } catch (error) { - throw new Error(`[PROFILE::FOLLOW] ERROR: ${(error as FetchError).data}`) - } -} - export const follow = async (followRequest: FollowRequest) => { try { const response = await api('/follow', { From 9ca8c9f216810307070320596b13c1c8c5bf843d Mon Sep 17 00:00:00 2001 From: hassnian Date: Wed, 12 Jun 2024 12:33:52 +0500 Subject: [PATCH 09/14] add(profiles): add delete profile confirm safety delay seconds --- components/profile/create/stages/Form.vue | 68 ++++++++++++++++------- locales/en.json | 3 +- 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/components/profile/create/stages/Form.vue b/components/profile/create/stages/Form.vue index d2a1b7f029..c152ff3793 100644 --- a/components/profile/create/stages/Form.vue +++ b/components/profile/create/stages/Form.vue @@ -101,24 +101,28 @@ data-testid="create-profile-submit-button" @click="emit('submit', form)" /> - -
- {{ - !deleteConfirm - ? $t('profiles.delete') - : $t('profiles.deleteConfirm') - }} - - -
-
+
@@ -160,6 +164,8 @@ const socialLinks = [ }, ] +const DELETE_CONFIRM_SAFETY_DELAY = 3000 + const emit = defineEmits<{ (e: 'submit', value: ProfileFormData): void (e: 'delete', address: string): void @@ -172,16 +178,38 @@ const props = defineProps<{ const deleteConfirm = ref() const now = useNow() +const { $i18n } = useNuxtApp() const { accountId } = useAuth() const profile = inject<{ userProfile: Ref; hasProfile: Ref }>( 'userProfile', ) -const isDeleteConfirmDisabled = computed(() => +const isDeleteConfirmSafetyDelay = computed(() => deleteConfirm.value - ? now.value.getTime() - deleteConfirm.value.getTime() < 3000 + ? now.value.getTime() - deleteConfirm.value.getTime() < + DELETE_CONFIRM_SAFETY_DELAY : false, ) + +const deleteConfirmSafetyDelayText = computed(() => { + if (isDeleteConfirmSafetyDelay.value && deleteConfirm.value) { + return $i18n.t('profiles.waitSeconds', [ + Math.ceil( + (deleteConfirm.value.getTime() + + DELETE_CONFIRM_SAFETY_DELAY - + now.value.getTime()) / + 1000, + ), + ]) + } +}) + +const deleteConfirmText = computed(() => + !deleteConfirm.value + ? $i18n.t('profiles.delete') + : $i18n.t('profiles.deleteConfirm'), +) + const substrateAddress = computed(() => formatAddress(accountId.value, 42)) const form = reactive({ address: substrateAddress.value, diff --git a/locales/en.json b/locales/en.json index 9796833e9e..49305b2cee 100644 --- a/locales/en.json +++ b/locales/en.json @@ -2093,6 +2093,7 @@ } }, "delete": "Reset all Fields - start over", - "deleteConfirm": "You sure? - click again" + "deleteConfirm": "You sure? - click again", + "waitSeconds": "Wait {0} Seconds" } } From 075780f025e9c936fb3f5b1cd3c74c8a2ab2b99d Mon Sep 17 00:00:00 2001 From: hassnian Date: Fri, 14 Jun 2024 10:31:09 +0500 Subject: [PATCH 10/14] add(ProfileCreateModal.vue): add profile clear notification --- components/profile/create/Modal.vue | 3 +++ locales/en.json | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/components/profile/create/Modal.vue b/components/profile/create/Modal.vue index 24dcc714dc..e9a953c524 100644 --- a/components/profile/create/Modal.vue +++ b/components/profile/create/Modal.vue @@ -121,6 +121,9 @@ const processProfile = async (profileData: ProfileFormData) => { const handleProfileDelete = async (address: string) => { try { await deleteProfile(address) + infoMessage($i18n.t('profiles.profileHasBeenCleared'), { + title: $i18n.t('profiles.profileReset'), + }) emit('deleted') close() } catch (error) { diff --git a/locales/en.json b/locales/en.json index 49305b2cee..d6fa5f0358 100644 --- a/locales/en.json +++ b/locales/en.json @@ -2094,6 +2094,8 @@ }, "delete": "Reset all Fields - start over", "deleteConfirm": "You sure? - click again", - "waitSeconds": "Wait {0} Seconds" + "waitSeconds": "Wait {0} Seconds", + "profileReset": "Profile Reset", + "profileHasBeenCleared": "Your profile has been cleared successfully. Start fresh!" } } From ccc77f10587d4d3e9e0c806fccfd42675ed244b5 Mon Sep 17 00:00:00 2001 From: hassnian Date: Thu, 20 Jun 2024 11:50:43 +0500 Subject: [PATCH 11/14] update(profiles): upload image with address and type --- components/profile/create/Modal.vue | 12 +++++++++--- services/imageWorker.ts | 10 +++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/components/profile/create/Modal.vue b/components/profile/create/Modal.vue index b2eee5885d..364e480b0d 100644 --- a/components/profile/create/Modal.vue +++ b/components/profile/create/Modal.vue @@ -52,6 +52,7 @@ const props = defineProps<{ const documentVisibility = useDocumentVisibility() const { $i18n } = useNuxtApp() +const { accountId } = useAuth() const profile = inject<{ hasProfile: Ref }>('userProfile') @@ -73,8 +74,13 @@ const close = () => { const uploadProfileImage = async ( file: File | null, + type: 'image' | 'banner', ): Promise => - file ? await uploadImage(file).then((response) => response.url) : undefined + file + ? await uploadImage({ file, type, address: accountId.value }).then( + (response) => response.url, + ) + : undefined const constructSocials = (profileData: ProfileFormData): SocialLink[] => { return [ @@ -98,11 +104,11 @@ const constructSocials = (profileData: ProfileFormData): SocialLink[] => { const processProfile = async (profileData: ProfileFormData) => { const imageUrl = profileData.image - ? await uploadProfileImage(profileData.image) + ? await uploadProfileImage(profileData.image, 'image') : profileData.imagePreview const bannerUrl = profileData.banner - ? await uploadProfileImage(profileData.banner) + ? await uploadProfileImage(profileData.banner, 'banner') : profileData.bannerPreview const profileBody: CreateProfileRequest | UpdateProfileRequest = { diff --git a/services/imageWorker.ts b/services/imageWorker.ts index aba3e1161d..836fd2c1c8 100644 --- a/services/imageWorker.ts +++ b/services/imageWorker.ts @@ -41,10 +41,18 @@ export async function getMetadata(url: string) { return metadata as unknown as NFTWithMetadata } -export const uploadImage = async (file: File) => { +type UploadImage = { + file: File + type: string + address: string +} + +export const uploadImage = async ({ file, type, address }: UploadImage) => { try { const form = new FormData() form.append('file', file) + form.append('address', address) + form.append('type', type) workerUrl.pathname = '/image/upload' From 66c7d2c635f0fa458e1bee040d9b8822b268c77a Mon Sep 17 00:00:00 2001 From: hassnian Date: Mon, 1 Jul 2024 09:45:35 +0500 Subject: [PATCH 12/14] ref(profiles): add upload image --- components/profile/create/Modal.vue | 25 ++- pnpm-lock.yaml | 226 ++++++++++++++-------------- services/imageWorker.ts | 26 ---- services/profile.ts | 38 +++++ 4 files changed, 172 insertions(+), 143 deletions(-) diff --git a/components/profile/create/Modal.vue b/components/profile/create/Modal.vue index 2d0b41714f..834bf16c6b 100644 --- a/components/profile/create/Modal.vue +++ b/components/profile/create/Modal.vue @@ -39,8 +39,8 @@ import { createProfile, deleteProfile, updateProfile, + uploadImage, } from '@/services/profile' -import { uploadImage } from '@/services/imageWorker' import { appClient, createChannel } from '@/services/farcaster' import { StatusAPIResponse } from '@farcaster/auth-client' import { useDocumentVisibility } from '@vueuse/core' @@ -75,12 +75,23 @@ const close = () => { const uploadProfileImage = async ( file: File | null, type: 'image' | 'banner', -): Promise => - file - ? await uploadImage({ file, type, address: accountId.value }).then( - (response) => response.url, - ) - : undefined +): Promise => { + if (!file) { + return undefined + } + + const { signature, message } = await getSignaturePair() + + const response = await uploadImage({ + file, + type, + address: accountId.value, + signature, + message, + }) + + return response.url +} const constructSocials = (profileData: ProfileFormData): SocialLink[] => { return [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bdbee09c50..28fc5dfd6d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true @@ -3844,7 +3844,7 @@ packages: resolution: {integrity: sha512-yyHHAj4G8pQIDfaIsMvQpwKMboIZtcHTUvPqXjOHyldh1O1vZfH4W03VDPv5RvI9P6DLTzJQlmVgj9wCf7c2Fw==} peerDependencies: '@fortawesome/fontawesome-svg-core': ~1 || ~6 - vue: 3.4.8 + vue: '>= 3.0.0 < 4' dependencies: '@fortawesome/fontawesome-svg-core': 6.5.2 vue: 3.4.8(typescript@5.4.5) @@ -3963,7 +3963,7 @@ packages: resolution: {integrity: sha512-DVGoYXPDcoJ55crD4cAXA0OGAS8fWHvKjvOApY1NLLN4DgRjxqHBVE6xCX/9Ai1VTM3izG6FJgXiXAbRVgv5KQ==} peerDependencies: histoire: ^0.17.6 - vue: 3.4.8 + vue: ^3.2.47 dependencies: '@histoire/controls': 0.17.15(vite@4.5.3) '@histoire/shared': 0.17.15(vite@4.5.3) @@ -4580,7 +4580,7 @@ packages: qr-code-styling: 1.6.0-rc.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-i18next: 13.5.0(i18next@22.5.1)(react-dom@18.2.0)(react-native@0.74.1)(react@18.2.0) + react-i18next: 13.5.0(i18next@23.11.5)(react-dom@18.2.0)(react-native@0.74.1)(react@18.2.0) react-native: 0.74.1(@babel/core@7.24.7)(@babel/preset-env@7.24.4)(react@18.2.0) dev: false @@ -4604,7 +4604,7 @@ packages: qr-code-styling: 1.6.0-rc.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-i18next: 13.5.0(i18next@22.5.1)(react-dom@18.2.0)(react-native@0.74.1)(react@18.2.0) + react-i18next: 13.5.0(i18next@23.11.5)(react-dom@18.2.0)(react-native@0.74.1)(react@18.2.0) react-native: 0.74.1(@babel/core@7.24.7)(@babel/preset-env@7.24.4)(react@18.2.0) dev: false @@ -5613,7 +5613,7 @@ packages: resolution: {integrity: sha512-gE7bKxbnd3OdlCHdZKgnbs2oOdcLHvEQ92LGnDCs9rCdsXazhQ7gcfow+FsKMp9MMu785O55gd4CiIgnn7N0BA==} engines: {node: ^14.18.0 || >=16.10.0} peerDependencies: - vue: 3.4.8 + vue: ^3.3.4 dependencies: '@nuxt/kit': 3.12.2(magicast@0.3.4)(rollup@4.14.0) '@rollup/plugin-replace': 5.0.7(rollup@4.14.0) @@ -5906,7 +5906,7 @@ packages: /@oruga-ui/oruga-next@0.7.0(vue@3.4.8): resolution: {integrity: sha512-T2KnNhGzgqv/Xzu4Efx3wnYahANcP6Z7Yc8DHOFIOLrM+ZDdTS9OjL3gofBVDrDBRg1DQv6EvsSsNkwMR88LpA==} peerDependencies: - vue: 3.4.8 + vue: ^3.0.0 dependencies: vue: 3.4.8(typescript@5.4.5) dev: true @@ -6270,15 +6270,15 @@ packages: - utf-8-validate dev: false - /@polkadot/api-augment@11.3.1: - resolution: {integrity: sha512-Yj+6rb6h0WwY3yJ+UGhjGW+tyMRFUMsKQuGw+eFsXdjiNU9UoXsAqA2dG7Q1F+oeX/g+y2gLGBezNoCwbl6HfA==} + /@polkadot/api-augment@12.0.2: + resolution: {integrity: sha512-l/0yoyhSWtHGkyrJvY9Pw78b8j0Now8f/4G4A2maeBL2U4OBjbGzwykPHpfTKeRfiulsUi408e6iWN94AjXxLA==} engines: {node: '>=18'} dependencies: - '@polkadot/api-base': 11.3.1 - '@polkadot/rpc-augment': 11.3.1 - '@polkadot/types': 11.3.1 - '@polkadot/types-augment': 11.3.1 - '@polkadot/types-codec': 11.3.1 + '@polkadot/api-base': 12.0.2 + '@polkadot/rpc-augment': 12.0.2 + '@polkadot/types': 12.0.2 + '@polkadot/types-augment': 12.0.2 + '@polkadot/types-codec': 12.0.2 '@polkadot/util': 12.6.2 tslib: 2.6.2 transitivePeerDependencies: @@ -6350,12 +6350,12 @@ packages: - utf-8-validate dev: false - /@polkadot/api-base@11.3.1: - resolution: {integrity: sha512-b8UkNL00NN7+3QaLCwL5cKg+7YchHoKCAhwKusWHNBZkkO6Oo2BWilu0dZkPJOyqV9P389Kbd9+oH+SKs9u2VQ==} + /@polkadot/api-base@12.0.2: + resolution: {integrity: sha512-LAaI6iyzlefrzJpEQb0YYtD1P9m9Hgx1iQY+Cov1uZzw6MYIlBQBBrbGzPS96NC7UxMSoeIv7GDMRPk/HCHf0A==} engines: {node: '>=18'} dependencies: - '@polkadot/rpc-core': 11.3.1 - '@polkadot/types': 11.3.1 + '@polkadot/rpc-core': 12.0.2 + '@polkadot/types': 12.0.2 '@polkadot/util': 12.6.2 rxjs: 7.8.1 tslib: 2.6.2 @@ -6434,16 +6434,16 @@ packages: - utf-8-validate dev: false - /@polkadot/api-derive@11.3.1: - resolution: {integrity: sha512-9dopzrh4cRuft1nANmBvMY/hEhFDu0VICMTOGxQLOl8NMfcOFPTLAN0JhSBUoicGZhV+c4vpv01NBx/7/IL1HA==} + /@polkadot/api-derive@12.0.2: + resolution: {integrity: sha512-i3AnOSHLw7DNLd+byxekQQxKRWseZANZnyg2F29kVBBDu6Fy7G6UaE2zPZaxS7w6wSqmTeB1u86WSvRocjkETw==} engines: {node: '>=18'} dependencies: - '@polkadot/api': 11.3.1 - '@polkadot/api-augment': 11.3.1 - '@polkadot/api-base': 11.3.1 - '@polkadot/rpc-core': 11.3.1 - '@polkadot/types': 11.3.1 - '@polkadot/types-codec': 11.3.1 + '@polkadot/api': 12.0.2 + '@polkadot/api-augment': 12.0.2 + '@polkadot/api-base': 12.0.2 + '@polkadot/rpc-core': 12.0.2 + '@polkadot/types': 12.0.2 + '@polkadot/types-codec': 12.0.2 '@polkadot/util': 12.6.2 '@polkadot/util-crypto': 12.6.2(@polkadot/util@12.6.2) rxjs: 7.8.1 @@ -6547,22 +6547,22 @@ packages: - utf-8-validate dev: false - /@polkadot/api@11.3.1: - resolution: {integrity: sha512-q4kFIIHTLvKxM24b0Eo8hJevsPMme+aITJGrDML9BgdZYTRN14+cu5nXiCsQvaEamdyYj+uCXWe2OV9X7pPxsA==} + /@polkadot/api@12.0.2: + resolution: {integrity: sha512-6JSCQ8scZNETos07CGoP8WehoEUDmXgRxWn4ce9O1kn/j5i6xCZj/dLp3/OPfh5ERYvVELRZr90bxUglckPj8w==} engines: {node: '>=18'} dependencies: - '@polkadot/api-augment': 11.3.1 - '@polkadot/api-base': 11.3.1 - '@polkadot/api-derive': 11.3.1 + '@polkadot/api-augment': 12.0.2 + '@polkadot/api-base': 12.0.2 + '@polkadot/api-derive': 12.0.2 '@polkadot/keyring': 12.6.2(@polkadot/util-crypto@12.6.2)(@polkadot/util@12.6.2) - '@polkadot/rpc-augment': 11.3.1 - '@polkadot/rpc-core': 11.3.1 - '@polkadot/rpc-provider': 11.3.1 - '@polkadot/types': 11.3.1 - '@polkadot/types-augment': 11.3.1 - '@polkadot/types-codec': 11.3.1 - '@polkadot/types-create': 11.3.1 - '@polkadot/types-known': 11.3.1 + '@polkadot/rpc-augment': 12.0.2 + '@polkadot/rpc-core': 12.0.2 + '@polkadot/rpc-provider': 12.0.2 + '@polkadot/types': 12.0.2 + '@polkadot/types-augment': 12.0.2 + '@polkadot/types-codec': 12.0.2 + '@polkadot/types-create': 12.0.2 + '@polkadot/types-known': 12.0.2 '@polkadot/util': 12.6.2 '@polkadot/util-crypto': 12.6.2(@polkadot/util@12.6.2) eventemitter3: 5.0.1 @@ -6902,13 +6902,13 @@ packages: - utf-8-validate dev: false - /@polkadot/rpc-augment@11.3.1: - resolution: {integrity: sha512-2PaDcKNju4QYQpxwVkWbRU3M0t340nMX9cMo+8awgvgL1LliV/fUDZueMKLuSS910JJMTPQ7y2pK4eQgMt08gQ==} + /@polkadot/rpc-augment@12.0.2: + resolution: {integrity: sha512-aQ4j3ifvT+co2tkMSS4iEdNhCunTtObXD1r2+jS9iLz0d8IrpXr6fZ1FzLuM5s3ijh8QpFBHiAs514+Wj9TNww==} engines: {node: '>=18'} dependencies: - '@polkadot/rpc-core': 11.3.1 - '@polkadot/types': 11.3.1 - '@polkadot/types-codec': 11.3.1 + '@polkadot/rpc-core': 12.0.2 + '@polkadot/types': 12.0.2 + '@polkadot/types-codec': 12.0.2 '@polkadot/util': 12.6.2 tslib: 2.6.2 transitivePeerDependencies: @@ -6978,13 +6978,13 @@ packages: - utf-8-validate dev: false - /@polkadot/rpc-core@11.3.1: - resolution: {integrity: sha512-KKNepsDd/mpmXcA6v/h14eFFPEzLGd7nrvx2UUXUxoZ0Fq2MH1hplP3s93k1oduNY/vOXJR2K9S4dKManA6GVQ==} + /@polkadot/rpc-core@12.0.2: + resolution: {integrity: sha512-lizSfchCaBjvNRZG7gELy3HKvvrLqLC4sMBg/1Y0zcsyZork8MxBkcVKgHQGBJ5BWLaoRrRKESC5DLe7DzZ2fA==} engines: {node: '>=18'} dependencies: - '@polkadot/rpc-augment': 11.3.1 - '@polkadot/rpc-provider': 11.3.1 - '@polkadot/types': 11.3.1 + '@polkadot/rpc-augment': 12.0.2 + '@polkadot/rpc-provider': 12.0.2 + '@polkadot/types': 12.0.2 '@polkadot/util': 12.6.2 rxjs: 7.8.1 tslib: 2.6.2 @@ -7097,13 +7097,13 @@ packages: - utf-8-validate dev: false - /@polkadot/rpc-provider@11.3.1: - resolution: {integrity: sha512-pqERChoHo45hd3WAgW8UuzarRF+G/o/eXEbl0PXLubiayw4X4qCmIzmtntUcKYgxGNcYGZaG87ZU8OjN97m6UA==} + /@polkadot/rpc-provider@12.0.2: + resolution: {integrity: sha512-WyQ9MxTYFAn0EH+2+2ZpzNwEFbnlCDedTW9bzrqGhT3RofYGfX/tpHzgQAyC4FSiCFA001gbwqskfDjVfpdy3A==} engines: {node: '>=18'} dependencies: '@polkadot/keyring': 12.6.2(@polkadot/util-crypto@12.6.2)(@polkadot/util@12.6.2) - '@polkadot/types': 11.3.1 - '@polkadot/types-support': 11.3.1 + '@polkadot/types': 12.0.2 + '@polkadot/types-support': 12.0.2 '@polkadot/util': 12.6.2 '@polkadot/util-crypto': 12.6.2(@polkadot/util@12.6.2) '@polkadot/x-fetch': 12.6.2 @@ -7197,12 +7197,12 @@ packages: tslib: 2.6.2 dev: false - /@polkadot/types-augment@11.3.1: - resolution: {integrity: sha512-eR3HVpvUmB3v7q2jTWVmVfAVfb1/kuNn7ij94Zqadg/fuUq0pKqIOKwkUj3OxRM3A/5BnW3MbgparjKD3r+fyw==} + /@polkadot/types-augment@12.0.2: + resolution: {integrity: sha512-vCDqcuVBTr2PTFf2YNolt/hbNx9sC3xzeYtGsYeBV7QfiljI324P0yLH1oeunFrIgI+G6OxiL4DIJcXzlyXEPQ==} engines: {node: '>=18'} dependencies: - '@polkadot/types': 11.3.1 - '@polkadot/types-codec': 11.3.1 + '@polkadot/types': 12.0.2 + '@polkadot/types-codec': 12.0.2 '@polkadot/util': 12.6.2 tslib: 2.6.2 dev: false @@ -7254,8 +7254,8 @@ packages: tslib: 2.6.2 dev: false - /@polkadot/types-codec@11.3.1: - resolution: {integrity: sha512-i7IiiuuL+Z/jFoKTA9xeh4wGQnhnNNjMT0+1ohvlOvnFsoKZKFQQOaDPPntGJVL1JDCV+KjkN2uQKZSeW8tguQ==} + /@polkadot/types-codec@12.0.2: + resolution: {integrity: sha512-W86PRqQf/8NW16+QszocizA1DbKwjMcbdecQJU57rbg340JuVMooDxaGuW6S2nRUFKwyVEmwVolDXUOYJcp8Vw==} engines: {node: '>=18'} dependencies: '@polkadot/util': 12.6.2 @@ -7307,11 +7307,11 @@ packages: tslib: 2.6.2 dev: false - /@polkadot/types-create@11.3.1: - resolution: {integrity: sha512-pBXtpz5FehcRJ6j5MzFUIUN8ZWM7z6HbqK1GxBmYbJVRElcGcOg7a/rL2pQVphU0Rx1E8bSO4thzGf4wUxSX7w==} + /@polkadot/types-create@12.0.2: + resolution: {integrity: sha512-2spRoI5PqezBXU3BFEwtvm6m/jwqmUzT2px0FIq8YZYzNWgmkew+oDAft89MXV+wGqutyO4BlASUGy5d3C6XNw==} engines: {node: '>=18'} dependencies: - '@polkadot/types-codec': 11.3.1 + '@polkadot/types-codec': 12.0.2 '@polkadot/util': 12.6.2 tslib: 2.6.2 dev: false @@ -7358,14 +7358,14 @@ packages: tslib: 2.6.2 dev: false - /@polkadot/types-known@11.3.1: - resolution: {integrity: sha512-3BIof7u6tn9bk3ZCIxA07iNoQ3uj4+vn3DTOjCKECozkRlt6V+kWRvqh16Hc0SHMg/QjcMb2fIu/WZhka1McUQ==} + /@polkadot/types-known@12.0.2: + resolution: {integrity: sha512-9Au2gVyjLXIJSoFzLs9eQdP5Z1L0J4ezTLWvUGZ5gQYhi19WViSUVYMDVn1wYJpYbHtVX6ee0XqyLQGcohCHDg==} engines: {node: '>=18'} dependencies: '@polkadot/networks': 12.6.2 - '@polkadot/types': 11.3.1 - '@polkadot/types-codec': 11.3.1 - '@polkadot/types-create': 11.3.1 + '@polkadot/types': 12.0.2 + '@polkadot/types-codec': 12.0.2 + '@polkadot/types-create': 12.0.2 '@polkadot/util': 12.6.2 tslib: 2.6.2 dev: false @@ -7438,8 +7438,8 @@ packages: tslib: 2.6.2 dev: false - /@polkadot/types-support@11.3.1: - resolution: {integrity: sha512-jTFz1GKyF7nI29yIOq4v0NiWTOf5yX4HahJNeFD8TcxoLhF+6tH/XXqrUXJEfbaTlSrRWiW1LZYlb+snctqKHA==} + /@polkadot/types-support@12.0.2: + resolution: {integrity: sha512-RhVoHJvxhnlhxt1asa9W3gExZpwSJkRb9YXVAoRdx1zk920j+PzJfBv/OTEaXgtHcIUrbVbddJtyMEOlNMMVdA==} engines: {node: '>=18'} dependencies: '@polkadot/util': 12.6.2 @@ -7504,14 +7504,14 @@ packages: tslib: 2.6.2 dev: false - /@polkadot/types@11.3.1: - resolution: {integrity: sha512-5c7uRFXQTT11Awi6T0yFIdAfD6xGDAOz06Kp7M5S9OGNZY28wSPk5x6BYfNphWPaIBmHHewYJB5qmnrdYQAWKQ==} + /@polkadot/types@12.0.2: + resolution: {integrity: sha512-qjbmu8p1qoueZak+kTdI6zKuRB4DIDA3bTWsHTmgeoWlZrSHcTJuYq8VjGDete+1S2+ZqEiVYXB4+CEzWQeehA==} engines: {node: '>=18'} dependencies: '@polkadot/keyring': 12.6.2(@polkadot/util-crypto@12.6.2)(@polkadot/util@12.6.2) - '@polkadot/types-augment': 11.3.1 - '@polkadot/types-codec': 11.3.1 - '@polkadot/types-create': 11.3.1 + '@polkadot/types-augment': 12.0.2 + '@polkadot/types-codec': 12.0.2 + '@polkadot/types-create': 12.0.2 '@polkadot/util': 12.6.2 '@polkadot/util-crypto': 12.6.2(@polkadot/util@12.6.2) rxjs: 7.8.1 @@ -7752,7 +7752,7 @@ packages: peerDependencies: '@polkadot/util': '*' '@polkadot/util-crypto': '*' - vue: 3.4.8 + vue: '>= 2.7' dependencies: '@polkadot/ui-shared': 3.6.6(@polkadot/util-crypto@12.6.2)(@polkadot/util@12.6.2) '@polkadot/util': 12.6.2 @@ -9397,7 +9397,7 @@ packages: /@subsocial/definitions@0.8.14: resolution: {integrity: sha512-K/8ZYGMyy15QI16bxgi0GfxP3JsnKeNAyPlwom1kDE89RGGs5O++PuWbXxVMMSVYfh9zn9qJYKiThBYIj/Vohg==} dependencies: - '@polkadot/api': 11.3.1 + '@polkadot/api': 12.0.2 lodash.camelcase: 4.3.0 transitivePeerDependencies: - bufferutil @@ -9575,7 +9575,7 @@ packages: resolution: {integrity: sha512-WogAH4+xDPWbiK9CUXAE4cQiCyvWeYZI3g3/onKbkb3tVnoEPRhbGHANgxpfAEFY165Vj4afKnI3hkVQvr7aHA==} peerDependencies: '@vue/composition-api': ^1.1.2 - vue: 3.4.8 + vue: ^2.6.0 || ^3.3.0 peerDependenciesMeta: '@vue/composition-api': optional: true @@ -10360,7 +10360,7 @@ packages: /@unhead/vue@1.9.14(vue@3.4.8): resolution: {integrity: sha512-Yc7Qv0ze+iLte4urHiA+ghkF7y+svrawrT+ZrCuGXkZ/eRTF/AY2SKex+rJQJZsP+fKEQ2pGb72IsI5kHFZT3A==} peerDependencies: - vue: 3.4.8 + vue: '>=2.7 || >=3' dependencies: '@unhead/schema': 1.9.14 '@unhead/shared': 1.9.14 @@ -10372,7 +10372,7 @@ packages: /@unhead/vue@1.9.4(vue@3.4.8): resolution: {integrity: sha512-F37bDhhieWQJyXvFV8NmrOXoIVJMhxVI/0ZUDrI9uTkMCofjfKWDJ+Gz0iYdhYF9mjQ5BN+pM31Zpxi+fN5Cfg==} peerDependencies: - vue: 3.4.8 + vue: '>=2.7 || >=3' dependencies: '@unhead/schema': 1.9.4 '@unhead/shared': 1.9.4 @@ -10726,7 +10726,7 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: vite: ^5.0.0 - vue: 3.4.8 + vue: ^3.0.0 dependencies: '@babel/core': 7.24.7 '@babel/plugin-transform-typescript': 7.24.7(@babel/core@7.24.7) @@ -10742,7 +10742,7 @@ packages: engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.0.0 || ^5.0.0 - vue: 3.4.8 + vue: ^3.2.25 dependencies: vite: 4.5.3(@types/node@20.14.6)(sass@1.77.6) vue: 3.4.8(typescript@5.4.5) @@ -10753,7 +10753,7 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: vite: ^5.0.0 - vue: 3.4.8 + vue: ^3.2.25 dependencies: vite: 5.3.1(@types/node@20.14.6)(sass@1.77.6) vue: 3.4.8(typescript@5.4.5) @@ -10882,7 +10882,7 @@ packages: resolution: {integrity: sha512-WC66NPVh2mJWqm4L0l/u/cOqm4pNOIwVdMGnDYAH2rHcOWy5x68GkhpkYTBu1+xwCSeHWOQn1TCGGbD+98fFpA==} engines: {node: '>=16.14.0'} peerDependencies: - vue: 3.4.8 + vue: ^2.7.0 || ^3.2.25 peerDependenciesMeta: vue: optional: true @@ -10904,7 +10904,7 @@ packages: '@apollo/client': ^3.4.13 '@vue/composition-api': ^1.0.0 graphql: '>=15' - vue: 3.4.8 + vue: ^2.6.0 || ^3.1.0 peerDependenciesMeta: '@vue/composition-api': optional: true @@ -11090,7 +11090,7 @@ packages: /@vue/devtools-applet@7.1.3(@unocss/reset@0.60.2)(floating-vue@5.2.2)(unocss@0.60.2)(vite@5.3.1)(vue@3.4.8): resolution: {integrity: sha512-525h17FzUF7ssko/U+yeP5jv0HaGm3eI4dVqncWPRCLTDtOy1V+srjoxYqr5qnzx6AdIU2icPQF2KNomd9FGZw==} peerDependencies: - vue: 3.4.8 + vue: ^3.0.0 dependencies: '@vue/devtools-core': 7.1.3(vite@5.3.1)(vue@3.4.8) '@vue/devtools-kit': 7.1.3(vue@3.4.8) @@ -11138,7 +11138,7 @@ packages: /@vue/devtools-kit@7.1.3(vue@3.4.8): resolution: {integrity: sha512-NFskFSJMVCBXTkByuk2llzI3KD3Blcm7WqiRorWjD6nClHPgkH5BobDH08rfulqq5ocRt5xV+3qOT1Q9FXJrwQ==} peerDependencies: - vue: 3.4.8 + vue: ^3.0.0 dependencies: '@vue/devtools-shared': 7.1.3 hookable: 5.5.3 @@ -11160,7 +11160,7 @@ packages: '@unocss/reset': '>=0.50.0-0' floating-vue: '>=2.0.0-0' unocss: '>=0.50.0-0' - vue: 3.4.8 + vue: '>=3.0.0-0' dependencies: '@unocss/reset': 0.60.2 '@vue/devtools-shared': 7.1.3 @@ -11283,7 +11283,7 @@ packages: /@vueuse/head@2.0.0(vue@3.4.8): resolution: {integrity: sha512-ykdOxTGs95xjD4WXE4na/umxZea2Itl0GWBILas+O4oqS7eXIods38INvk3XkJKjqMdWPcpCyLX/DioLQxU1KA==} peerDependencies: - vue: 3.4.8 + vue: '>=2.7 || >=3' dependencies: '@unhead/dom': 1.9.4 '@unhead/schema': 1.9.4 @@ -12274,7 +12274,7 @@ packages: /@web3modal/scaffold-vue@4.2.2(react@18.2.0)(vue@3.4.8): resolution: {integrity: sha512-oaxOyaG5enjodIp/FNyodIPiFa3yiB4ptQrRfNX5opv6z2HUKDiu4jM3BRLwys2ncgLSflhDGfx3QQZZqNR8Hg==} peerDependencies: - vue: 3.4.8 + vue: '>=3' peerDependenciesMeta: vue: optional: true @@ -12370,7 +12370,7 @@ packages: react: '>=17' react-dom: '>=17' viem: '>=2.0.0' - vue: 3.4.8 + vue: '>=3' peerDependenciesMeta: react: optional: true @@ -16015,7 +16015,7 @@ packages: resolution: {integrity: sha512-afW+h2CFafo+7Y9Lvw/xsqjaQlKLdJV7h1fCHfcYQ1C4SVMlu7OAekqWgu5d4SgvkBVU0pVpLlVsrSTBURFRkg==} peerDependencies: '@nuxt/kit': ^3.2.0 - vue: 3.4.8 + vue: ^3.2.0 peerDependenciesMeta: '@nuxt/kit': optional: true @@ -16944,6 +16944,12 @@ packages: '@babel/runtime': 7.24.4 dev: false + /i18next@23.11.5: + resolution: {integrity: sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==} + dependencies: + '@babel/runtime': 7.24.4 + dev: false + /iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -20711,7 +20717,7 @@ packages: peerDependencies: '@vue/composition-api': ^1.4.0 typescript: '>=4.4.4' - vue: 3.4.8 + vue: ^2.6.14 || ^3.3.0 peerDependenciesMeta: '@vue/composition-api': optional: true @@ -21724,7 +21730,7 @@ packages: /qrcode.vue@3.4.1(vue@3.4.8): resolution: {integrity: sha512-wq/zHsifH4FJ1GXQi8/wNxD1KfQkckIpjK1KPTc/qwYU5/Bkd4me0w4xZSg6EXk6xLBkVDE0zxVagewv5EMAVA==} peerDependencies: - vue: 3.4.8 + vue: ^3.0.0 dependencies: vue: 3.4.8(typescript@5.4.5) dev: false @@ -21854,7 +21860,7 @@ packages: scheduler: 0.23.0 dev: false - /react-i18next@13.5.0(i18next@22.5.1)(react-dom@18.2.0)(react-native@0.74.1)(react@18.2.0): + /react-i18next@13.5.0(i18next@23.11.5)(react-dom@18.2.0)(react-native@0.74.1)(react@18.2.0): resolution: {integrity: sha512-CFJ5NDGJ2MUyBohEHxljOq/39NQ972rh1ajnadG9BjTk+UXbHLq4z5DKEbEQBDoIhUmmbuS/fIMJKo6VOax1HA==} peerDependencies: i18next: '>= 23.2.3' @@ -21869,7 +21875,7 @@ packages: dependencies: '@babel/runtime': 7.24.4 html-parse-stringify: 3.0.1 - i18next: 22.5.1 + i18next: 23.11.5 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) react-native: 0.74.1(@babel/core@7.24.7)(@babel/preset-env@7.24.4)(react@18.2.0) @@ -22863,7 +22869,7 @@ packages: /site-config-stack@2.2.12(vue@3.4.8): resolution: {integrity: sha512-U+nyw2vZ6E2zF/JYlFFEmDsqXSJbf0/6ZBCKXI4FZ2509iQwnEesfQXvWNuJ2JCemUJdAXAoiIturxEJtV4z0g==} peerDependencies: - vue: 3.4.8 + vue: ^3 dependencies: ufo: 1.5.3 vue: 3.4.8(typescript@5.4.5) @@ -24708,7 +24714,7 @@ packages: '@vue/composition-api': ^1.0.0-rc.1 typescript: '>=5.0.4' viem: 2.x - vue: 3.4.8 + vue: ^2.0.0 || >=3.0.0 peerDependenciesMeta: '@vue/composition-api': optional: true @@ -25118,7 +25124,7 @@ packages: /vite-svg-loader@5.1.0(vue@3.4.8): resolution: {integrity: sha512-M/wqwtOEjgb956/+m5ZrYT/Iq6Hax0OakWbokj8+9PXOnB7b/4AxESHieEtnNEy7ZpjsjYW1/5nK8fATQMmRxw==} peerDependencies: - vue: 3.4.8 + vue: '>=3.2.13' dependencies: svgo: 3.2.0 vue: 3.4.8(typescript@5.4.5) @@ -25427,7 +25433,7 @@ packages: resolution: {integrity: sha512-rZjqcHBxKiHrBl0CIvcOlVEBwRhpWAVf6rDU3vUfa7HuSRmGtCslc0Oc8m16oAVuk0erzc1FCtH1VCriHsrz+A==} peerDependencies: chart.js: ^4.1.1 - vue: 3.4.8 + vue: ^3.0.0-0 || ^2.7.0 dependencies: chart.js: 4.4.3 vue: 3.4.8(typescript@5.4.5) @@ -25440,7 +25446,7 @@ packages: requiresBuild: true peerDependencies: '@vue/composition-api': ^1.0.0-rc.1 - vue: 3.4.8 + vue: ^3.0.0-0 || ^2.6.0 peerDependenciesMeta: '@vue/composition-api': optional: true @@ -25455,7 +25461,7 @@ packages: requiresBuild: true peerDependencies: '@vue/composition-api': ^1.0.0-rc.1 - vue: 3.4.8 + vue: ^3.0.0-0 || ^2.6.0 peerDependenciesMeta: '@vue/composition-api': optional: true @@ -25469,7 +25475,7 @@ packages: /vue-dompurify-html@4.1.4(vue@3.4.8): resolution: {integrity: sha512-K0XDSZA4dmMMvAgW8yaCx1kAYQldmgXeHJaLPS0mlSKOu8B+onE06X4KfB5LGyX4jR3rlVosyWJczRBzR0sZ/g==} peerDependencies: - vue: 3.4.8 + vue: ^2.7.0 || ^3.0.0 dependencies: dompurify: 3.0.11 vue: 3.4.8(typescript@5.4.5) @@ -25518,7 +25524,7 @@ packages: resolution: {integrity: sha512-vU4gY6lu8Pdfs9BgKGiDAJmFDf88cceR47KcSB0VW4xJzUrXR/7qwqM7A8dQ2nedhoIDxoOm5Ro4pFd2KvJqbA==} engines: {node: '>= 16'} peerDependencies: - vue: 3.4.8 + vue: ^3.0.0 dependencies: '@intlify/core-base': 9.11.0 '@intlify/shared': 9.11.0 @@ -25529,7 +25535,7 @@ packages: /vue-observe-visibility@2.0.0-alpha.1(vue@3.4.8): resolution: {integrity: sha512-flFbp/gs9pZniXR6fans8smv1kDScJ8RS7rEpMjhVabiKeq7Qz3D9+eGsypncjfIyyU84saU88XZ0zjbD6Gq/g==} peerDependencies: - vue: 3.4.8 + vue: ^3.0.0 dependencies: vue: 3.4.8(typescript@5.4.5) dev: true @@ -25537,7 +25543,7 @@ packages: /vue-resize@2.0.0-alpha.1(vue@3.4.8): resolution: {integrity: sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==} peerDependencies: - vue: 3.4.8 + vue: ^3.0.0 dependencies: vue: 3.4.8(typescript@5.4.5) dev: true @@ -25545,7 +25551,7 @@ packages: /vue-router@4.3.0(vue@3.4.8): resolution: {integrity: sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ==} peerDependencies: - vue: 3.4.8 + vue: ^3.2.0 dependencies: '@vue/devtools-api': 6.6.1 vue: 3.4.8(typescript@5.4.5) @@ -25554,7 +25560,7 @@ packages: /vue-router@4.3.3(vue@3.4.8): resolution: {integrity: sha512-8Q+u+WP4N2SXY38FDcF2H1dUEbYVHVPtPCPZj/GTZx8RCbiB8AtJP9+YIxn4Vs0svMTNQcLIzka4GH7Utkx9xQ==} peerDependencies: - vue: 3.4.8 + vue: ^3.2.0 dependencies: '@vue/devtools-api': 6.6.1 vue: 3.4.8(typescript@5.4.5) @@ -25570,7 +25576,7 @@ packages: /vue-tippy@6.4.1(vue@3.4.8): resolution: {integrity: sha512-PEAKdioZjUvYWz4euxHFSXKJbL6kIKO29/LtQaCBbnd5Vg0U5kL8iLuqRshB2I31pXPSQS0qJsWx56178eS2QA==} peerDependencies: - vue: 3.4.8 + vue: ^3.2.0 dependencies: tippy.js: 6.3.7 vue: 3.4.8(typescript@5.4.5) @@ -25579,7 +25585,7 @@ packages: /vue-virtual-scroller@2.0.0-beta.8(vue@3.4.8): resolution: {integrity: sha512-b8/f5NQ5nIEBRTNi6GcPItE4s7kxNHw2AIHLtDp+2QvqdTjVN0FgONwX9cr53jWRgnu+HRLPaWDOR2JPI5MTfQ==} peerDependencies: - vue: 3.4.8 + vue: ^3.2.0 dependencies: mitt: 2.1.0 vue: 3.4.8(typescript@5.4.5) @@ -25591,7 +25597,7 @@ packages: resolution: {integrity: sha512-0KEU+t1ZcOFomf2xhpKPMU2XBdZDVyVjvkxcXAJRpZ818Om4MMhKxbrmIK6hZ+NZOyoRAgMHl/N1zFZJuQoSJA==} engines: {node: '>=14.6'} peerDependencies: - vue: 3.4.8 + vue: '>=3.0.0' dependencies: vue: 3.4.8(typescript@5.4.5) dev: false diff --git a/services/imageWorker.ts b/services/imageWorker.ts index 836fd2c1c8..266855e097 100644 --- a/services/imageWorker.ts +++ b/services/imageWorker.ts @@ -40,29 +40,3 @@ export async function getMetadata(url: string) { return metadata as unknown as NFTWithMetadata } - -type UploadImage = { - file: File - type: string - address: string -} - -export const uploadImage = async ({ file, type, address }: UploadImage) => { - try { - const form = new FormData() - form.append('file', file) - form.append('address', address) - form.append('type', type) - - workerUrl.pathname = '/image/upload' - - const response = await $fetch<{ url: string }>(workerUrl.toString(), { - method: 'POST', - body: form, - }) - - return response - } catch (error) { - throw new Error(`[IMAGE::UPLOAD] ERROR: ${error?.data}`) - } -} diff --git a/services/profile.ts b/services/profile.ts index 44f65ce17f..6dec93f23e 100644 --- a/services/profile.ts +++ b/services/profile.ts @@ -224,3 +224,41 @@ export const isFollowing = async ( ) } } + +type UploadImage = { + file: File + type: string + address: string + signature: string + message: string +} + +export const uploadImage = async ({ + file, + type, + address, + signature, + message, +}: UploadImage) => { + try { + address = toSubstrateAddress(address) + + const form = new FormData() + form.append('file', file) + form.append('address', address) + form.append('type', type) + form.append('signature', signature) + form.append('message', message) + + const response = await api<{ url: string }>(`/profiles/${address}/image`, { + method: 'POST', + body: form, + }) + + return response + } catch (error) { + throw new Error( + `[PROFILE::UPLOAD_IMAGe] ERROR: ${(error as FetchError).data}`, + ) + } +} From e5b10f3b079581179f11ff013a17d43eb9402238 Mon Sep 17 00:00:00 2001 From: hassnian Date: Mon, 1 Jul 2024 09:55:16 +0500 Subject: [PATCH 13/14] fix(profiles): delete profile missing signature --- components/profile/create/Modal.vue | 3 ++- services/profile.ts | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/components/profile/create/Modal.vue b/components/profile/create/Modal.vue index 834bf16c6b..28a892d464 100644 --- a/components/profile/create/Modal.vue +++ b/components/profile/create/Modal.vue @@ -144,7 +144,8 @@ const processProfile = async (profileData: ProfileFormData) => { const handleProfileDelete = async (address: string) => { try { - await deleteProfile(address) + const { signature, message } = await getSignaturePair() + await deleteProfile({ address, message, signature }) infoMessage($i18n.t('profiles.profileHasBeenCleared'), { title: $i18n.t('profiles.profileReset'), }) diff --git a/services/profile.ts b/services/profile.ts index 6dec93f23e..507e6129b7 100644 --- a/services/profile.ts +++ b/services/profile.ts @@ -165,10 +165,25 @@ export const updateProfile = async (updates: UpdateProfileRequest) => { } } -export const deleteProfile = async (address: string) => { +type DeleteProfile = { + message: string + signature: string + address: string +} + +export const deleteProfile = async ({ + address, + message, + signature, +}: DeleteProfile) => { try { const response = await api(`/profiles/${address}`, { method: 'DELETE', + body: { + message, + signature, + address, + }, }) return response } catch (error) { From dffdc13c59eace1b69f4c37bdb90ee988b9eec99 Mon Sep 17 00:00:00 2001 From: hassnian Date: Mon, 1 Jul 2024 09:59:03 +0500 Subject: [PATCH 14/14] fix(profiles): image upload throw error typo --- services/profile.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/profile.ts b/services/profile.ts index 507e6129b7..2ecb3a5683 100644 --- a/services/profile.ts +++ b/services/profile.ts @@ -273,7 +273,7 @@ export const uploadImage = async ({ return response } catch (error) { throw new Error( - `[PROFILE::UPLOAD_IMAGe] ERROR: ${(error as FetchError).data}`, + `[PROFILE::UPLOAD_IMAGE] ERROR: ${(error as FetchError).data}`, ) } }