Skip to content

Commit

Permalink
feat(tns): avatar and cover image upload previews (#782)
Browse files Browse the repository at this point in the history
  • Loading branch information
omniwired authored and ChiragPansuriya-iView committed Jan 31, 2024
1 parent 01c700b commit c8f7950
Show file tree
Hide file tree
Showing 18 changed files with 351 additions and 273 deletions.
Binary file added assets/default-images/tns-profile-cover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions assets/icons/upload-cloud.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"expo": "^49.0.16",
"expo-av": "~13.4.1",
"expo-barcode-scanner": "~12.5.3",
"expo-document-picker": "~11.5.4",
"expo-font": "~11.4.0",
"expo-linear-gradient": "~12.3.0",
"expo-optimize": "^0.2.20",
Expand Down
26 changes: 20 additions & 6 deletions packages/components/OptimizedImage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CID } from "multiformats";
import React, { memo } from "react";
import React, { memo, useEffect } from "react";
import { Image, ImageProps, View, StyleSheet, PixelRatio } from "react-native";

import { neutral33 } from "../utils/style/colors";
Expand All @@ -21,18 +21,32 @@ export const OptimizedImage: React.FC<
const sourceURI = shouldUseFallback ? fallbackURI : baseSourceURI;
const sourceWidth = PixelRatio.getPixelSizeForLayoutSize(width);
const sourceHeight = PixelRatio.getPixelSizeForLayoutSize(height);
const otherStyle = StyleSheet.flatten(other.style);

useEffect(() => {
setIsError(false);
}, [baseSourceURI]);

useEffect(() => {
setIsFallbackError(false);
}, [fallbackURI]);

if ((shouldUseFallback && !fallbackURI) || isFallbackError) {
return (
<View
style={{
width,
height,
borderRadius: StyleSheet.flatten(other.style).borderRadius,
borderTopLeftRadius: StyleSheet.flatten(other.style)
.borderTopLeftRadius,
borderBottomLeftRadius: StyleSheet.flatten(other.style)
.borderBottomLeftRadius,
position: otherStyle.position,
top: otherStyle.top,
left: otherStyle.left,
right: otherStyle.right,
bottom: otherStyle.bottom,
borderColor: otherStyle.borderColor,
borderWidth: otherStyle.borderWidth,
borderRadius: otherStyle.borderRadius,
borderTopLeftRadius: otherStyle.borderTopLeftRadius,
borderBottomLeftRadius: otherStyle.borderBottomLeftRadius,
backgroundColor: neutral33,
}}
/>
Expand Down
8 changes: 7 additions & 1 deletion packages/components/inputs/TextInputCustom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from "react-hook-form";
import {
ActivityIndicator,
Pressable,
StyleProp,
StyleSheet,
TextInput,
Expand Down Expand Up @@ -48,11 +49,14 @@ import { LegacyTertiaryBox } from "../boxes/LegacyTertiaryBox";
import { CustomPressable } from "../buttons/CustomPressable";
import { SpacerColumn, SpacerRow } from "../spacer";

// TODO: Refacto TextInputCustom. Too much props

export interface TextInputCustomProps<T extends FieldValues>
extends Omit<TextInputProps, "accessibilityRole" | "defaultValue"> {
label: string;
variant?: "regular" | "labelOutside" | "noStyle";
iconSVG?: React.FC<SvgProps>;
onPressChildren?: () => void;
placeHolder?: string;
squaresBackgroundColor?: string;
style?: StyleProp<ViewStyle>;
Expand Down Expand Up @@ -139,6 +143,7 @@ export const TextInputCustom = <T extends FieldValues>({
subtitle,
labelStyle,
iconSVG,
onPressChildren,
hideLabel,
valueModifier,
errorStyle,
Expand Down Expand Up @@ -292,9 +297,10 @@ export const TextInputCustom = <T extends FieldValues>({
{...restProps}
/>
</View>

{isLoading ? (
<ActivityIndicator color={secondaryColor} size="small" />
) : onPressChildren ? (
<Pressable onPress={onPressChildren}>{children}</Pressable>
) : (
<>{children}</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export const TNSNameFinderModal: React.FC<{
<ModalBase
visible={visible}
onClose={onClose}
label="Find a name"
label="Find a Name"
childrenBottom={<DomainsAvailability />}
width={372}
>
Expand Down
182 changes: 182 additions & 0 deletions packages/components/teritoriNameService/MediaPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import * as DocumentPicker from "expo-document-picker";
import { DocumentPickerResult } from "expo-document-picker";
import React, { Dispatch, SetStateAction, useState } from "react";
import { ActivityIndicator, View, ViewStyle } from "react-native";
import { useSelector } from "react-redux";

import tnsProfileAvatar from "../../../assets/default-images/default-name-nft.png";
import tnsProfileCover from "../../../assets/default-images/tns-profile-cover.png";
import uploadCloudIcon from "../../../assets/icons/upload-cloud.svg";
import { useFeedbacks } from "../../context/FeedbacksProvider";
import { Metadata } from "../../contracts-clients/teritori-name-service/TeritoriNameService.types";
import { PinataFileProps, useIpfs } from "../../hooks/useIpfs";
import { useSelectedNetworkInfo } from "../../hooks/useSelectedNetwork";
import useSelectedWallet from "../../hooks/useSelectedWallet";
import { selectNFTStorageAPI } from "../../store/slices/settings";
import { generateIpfsKey } from "../../utils/ipfs";
import {
neutral00,
neutral17,
neutral33,
secondaryColor,
} from "../../utils/style/colors";
import { layout } from "../../utils/style/layout";
import { OptimizedImage } from "../OptimizedImage";
import { SVG } from "../SVG";
import { TextInputCustom } from "../inputs/TextInputCustom";

export const MediaPreview: React.FC<{
style: ViewStyle;
avatarImageUrl: string;
setAvatarImageUrl: Dispatch<SetStateAction<string>>;
bannerImageUrl: string;
setBannerImageUrl: Dispatch<SetStateAction<string>>;
}> = ({
style,
avatarImageUrl,
setAvatarImageUrl,
bannerImageUrl,
setBannerImageUrl,
}) => {
const selectedNetwork = useSelectedNetworkInfo();
const selectedWallet = useSelectedWallet();
const { setToastError } = useFeedbacks();
const { pinataPinFileToIPFS } = useIpfs();
const userId = selectedWallet?.userId;
const [isAvatarImageUploadLoading, setAvatarImageUploadLoading] =
useState(false);
const [isBannerImageUploadLoading, setBannerImageUploadLoading] =
useState(false);
const userIPFSKey = useSelector(selectNFTStorageAPI);

const upload = async (
callback: Dispatch<SetStateAction<string>>,
documentPickerResult: DocumentPickerResult,
) => {
const pinataJWTKey =
userIPFSKey || (await generateIpfsKey(selectedNetwork?.id || "", userId));
if (!pinataJWTKey) {
console.error("upload file err : No Pinata JWT");
setToastError({
title: "File upload failed",
message: "No Pinata JWT",
});
return;
}
if (documentPickerResult.output) {
const fileIpfsHash = await pinataPinFileToIPFS({
pinataJWTKey,
file: { file: documentPickerResult.output[0] },
} as PinataFileProps);
callback(`ipfs://${fileIpfsHash}`);
}
};

const onPressUploadAvatar = async () => {
const documentPickerResult = await DocumentPicker.getDocumentAsync({
multiple: false,
});
setAvatarImageUploadLoading(true);
await upload(setAvatarImageUrl, documentPickerResult);
setAvatarImageUploadLoading(false);
};

const onPressUploadBanner = async () => {
const documentPickerResult = await DocumentPicker.getDocumentAsync({
multiple: false,
});
setBannerImageUploadLoading(true);
await upload(setBannerImageUrl, documentPickerResult);
setBannerImageUploadLoading(false);
};

return (
<View
style={{
borderRadius: layout.borderRadius,
borderWidth: 1,
marginBottom: layout.spacing_x1,
padding: layout.spacing_x1_5,
backgroundColor: neutral17,
width: "100%",
}}
>
<TextInputCustom<Metadata>
name="image"
style={style}
label="Avatar URL"
noBrokenCorners
variant="labelOutside"
placeHolder="https://website.com/avatar.jpg"
onPressChildren={onPressUploadAvatar}
value={avatarImageUrl}
onChangeText={setAvatarImageUrl}
squaresBackgroundColor={neutral17}
>
{isAvatarImageUploadLoading ? (
<ActivityIndicator size={16} color={secondaryColor} />
) : (
<SVG source={uploadCloudIcon} width={16} height={16} />
)}
</TextInputCustom>

<TextInputCustom<Metadata>
name="public_profile_header"
style={style}
label="Cover Image URL"
noBrokenCorners
onPressChildren={onPressUploadBanner}
variant="labelOutside"
placeHolder="https://website.com/coverimage.jpg"
value={bannerImageUrl}
onChangeText={setBannerImageUrl}
squaresBackgroundColor={neutral17}
>
{isBannerImageUploadLoading ? (
<ActivityIndicator size={16} color={secondaryColor} />
) : (
<SVG source={uploadCloudIcon} width={16} height={16} />
)}
</TextInputCustom>

<View
style={{
marginTop: layout.spacing_x1,
marginBottom: layout.spacing_x3,
}}
>
<OptimizedImage
sourceURI={bannerImageUrl || tnsProfileCover}
width={410}
height={120}
style={{
width: "100%",
height: 120,
minHeight: 120,
borderRadius: layout.borderRadius,
borderWidth: 1,
borderColor: neutral33,
}}
/>
<OptimizedImage
sourceURI={avatarImageUrl || tnsProfileAvatar}
resizeMode="cover"
width={52}
height={52}
style={{
width: 52,
minWidth: 52,
height: 52,
minHeight: 52,
position: "absolute",
bottom: -22,
left: 32,
borderRadius: 25,
borderWidth: 1,
borderColor: neutral00,
}}
/>
</View>
</View>
);
};
87 changes: 0 additions & 87 deletions packages/components/teritoriNameService/NameData.tsx

This file was deleted.

Loading

0 comments on commit c8f7950

Please sign in to comment.