Skip to content

Commit

Permalink
feat(front): add file upload and fetch (#72)
Browse files Browse the repository at this point in the history
* chore(kube): remove ingress class name on ingress

* feat(api): increase codec max in-memory size

* feat(front): add profile picture upload

* feat(front): add profile picture fetch

* feat(front): add resume upload

* feat(front): add resume fetch

* chore: run linter
  • Loading branch information
Kuruyia authored Feb 18, 2024
1 parent 55d6f14 commit 6f1ceb5
Show file tree
Hide file tree
Showing 18 changed files with 321 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.linkedout.backend.config

import org.springframework.context.annotation.Configuration
import org.springframework.http.codec.ServerCodecConfigurer
import org.springframework.web.reactive.config.WebFluxConfigurer

@Configuration
internal open class WebFluxConfiguration : WebFluxConfigurer {
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
configurer.defaultCodecs().maxInMemorySize(8 * 1024 * 1024)
}
}
2 changes: 2 additions & 0 deletions backend/api_gateway/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
spring:
codec:
max-in-memory-size: 8MB
security:
oauth2:
resourceserver:
Expand Down
7 changes: 6 additions & 1 deletion front/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
"experiments": {
"tsconfigPaths": true
},
"plugins": ["expo-localization", "expo-font", "expo-secure-store"]
"plugins": [
"expo-localization",
"expo-font",
"expo-secure-store",
"expo-document-picker"
]
}
}
12 changes: 11 additions & 1 deletion front/assets/translations/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,17 @@
"phoneNumber": "Phone number",
"email": "Email",
"references": "References",
"downloadResume": "Download resume",
"uploadPicture": "Upload profile picture",
"uploadResume": "Upload resume",
"shareResume": "Share resume",
"resumeNotFound": {
"title": "Resume not found",
"message": "You have not uploaded a resume yet."
},
"resumeDownloadError": {
"title": "Download error",
"message": "An error occurred while downloading the resume."
},
"information": "Information",
"firstName": "First name",
"lastName": "Last name",
Expand Down
12 changes: 11 additions & 1 deletion front/assets/translations/fr_FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,17 @@
"phoneNumber": "Numéro de téléphone",
"email": "Email",
"references": "Références",
"downloadResume": "Télécharger le CV",
"uploadPicture": "Modifier la photo de profil",
"uploadResume": "Modifier le CV",
"shareResume": "Partager le CV",
"resumeNotFound": {
"title": "Aucun CV trouvé",
"message": "Vous n'avez pas encore ajouté de CV à votre profil."
},
"resumeDownloadError": {
"title": "Erreur de téléchargement",
"message": "Une erreur est survenue lors du téléchargement de votre CV."
},
"information": "Informations",
"firstName": "Prénom",
"lastName": "Nom",
Expand Down
39 changes: 39 additions & 0 deletions front/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
"expo-auth-session": "~5.4.0",
"expo-crypto": "~12.8.0",
"expo-font": "~11.10.2",
"expo-image-picker": "~14.7.1",
"expo-localization": "~14.8.3",
"expo-secure-store": "~12.8.1",
"expo-splash-screen": "~0.26.4",
"expo-status-bar": "~1.11.1",
"expo-system-ui": "~2.9.3",
Expand All @@ -44,7 +46,9 @@
"react-native-tab-view": "^3.5.2",
"react-redux": "^9.1.0",
"typescript": "^5.3.3",
"expo-secure-store": "~12.8.1"
"expo-document-picker": "~11.10.1",
"expo-sharing": "~11.10.0",
"expo-file-system": "~16.0.6"
},
"devDependencies": {
"@babel/core": "^7.23.9",
Expand Down
64 changes: 64 additions & 0 deletions front/src/components/profile/ProfileShareResumeButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as FileSystem from 'expo-file-system';
import * as Sharing from 'expo-sharing';
import { useCallback } from 'react';
import { Alert } from 'react-native';
import { Button } from 'react-native-paper';

import { useAppSelector } from '@/store/hooks';
import i18n from '@/utils/i18n';

/**
* Button to share the resume of the user.
* @constructor
*/
export const ProfileShareResumeButton = () => {
// Store hooks
const auth = useAppSelector((state) => state.auth);

// Callbacks
const handleDownloadResume = useCallback(async () => {
if (auth.state !== 'authenticated') {
return;
}

const downloadResult = await FileSystem.downloadAsync(
`${process.env.EXPO_PUBLIC_API_URL}/profile/cv`,
FileSystem.cacheDirectory + 'cv.pdf',
{
headers: {
Authorization: `Bearer ${auth.token}`,
},
},
);

if (downloadResult.status !== 200) {
if (downloadResult.status === 404) {
Alert.alert(
i18n.t('profile.info.resumeNotFound.title'),
i18n.t('profile.info.resumeNotFound.message'),
);
} else {
Alert.alert(
i18n.t('profile.info.resumeDownloadError.title'),
i18n.t('profile.info.resumeDownloadError.message'),
);
}

return;
}

await Sharing.shareAsync(downloadResult.uri);
}, [auth]);

return (
<Button
mode='contained-tonal'
onPress={handleDownloadResume}
icon='file-document-outline'
>
{i18n.t('profile.info.shareResume')}
</Button>
);
};

export default ProfileShareResumeButton;
11 changes: 11 additions & 0 deletions front/src/components/profile/ProfileUpdateInfosForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { FC, useCallback } from 'react';
import { Image, StyleSheet, View, ViewStyle } from 'react-native';
import { Text, TextInput } from 'react-native-paper';

import ProfileUploadPictureButton from '@/components/profile/ProfileUploadPictureButton';
import ProfileUploadResumeButton from '@/components/profile/ProfileUploadResumeButton';
import i18n from '@/utils/i18n';

/**
Expand All @@ -27,6 +29,10 @@ const styles = StyleSheet.create({
textInput: {
marginVertical: 8,
},
uploadButtonContainer: {
gap: 8,
marginVertical: 8,
},
});

/**
Expand Down Expand Up @@ -127,6 +133,11 @@ const ProfileUpdateInfosForm: FC<ProfileUpdateInfosFormProps> = ({
onChangeText={(value) => handleInputChange('shortBiography', value)}
/>

<View style={styles.uploadButtonContainer}>
<ProfileUploadPictureButton />
<ProfileUploadResumeButton />
</View>

<Text variant='headlineMedium' style={styles.sectionTitle}>
{i18n.t('profile.info.contact')}
</Text>
Expand Down
43 changes: 43 additions & 0 deletions front/src/components/profile/ProfileUploadPictureButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as ImagePicker from 'expo-image-picker';
import { useCallback } from 'react';
import { Button } from 'react-native-paper';

import { useUploadProfilePictureMutation } from '@/store/api/profileApiSlice';
import i18n from '@/utils/i18n';

/**
* Button to upload the profile picture of the user.
* @constructor
*/
export const ProfileUploadPictureButton = () => {
// API calls
const [uploadProfilePicture] = useUploadProfilePictureMutation();

// Callbacks
const handleUploadPicture = useCallback(async () => {
const result = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
aspect: [1, 1],
exif: false,
quality: 0.4,
});

if (result.canceled === true || result.assets.length === 0) {
return;
}

uploadProfilePicture(result.assets[0].uri);
}, [uploadProfilePicture]);

return (
<Button
mode='contained-tonal'
onPress={handleUploadPicture}
icon='image-plus'
>
{i18n.t('profile.info.uploadPicture')}
</Button>
);
};

export default ProfileUploadPictureButton;
40 changes: 40 additions & 0 deletions front/src/components/profile/ProfileUploadResumeButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as DocumentPicker from 'expo-document-picker';
import { useCallback } from 'react';
import { Button } from 'react-native-paper';

import { useUploadResumeMutation } from '@/store/api/profileApiSlice';
import i18n from '@/utils/i18n';

/**
* Button to upload the resume of the user.
* @constructor
*/
export const ProfileUploadResumeButton = () => {
// API calls
const [uploadResume] = useUploadResumeMutation();

// Callbacks
const handleUploadResume = useCallback(async () => {
const result = await DocumentPicker.getDocumentAsync({
type: 'application/pdf',
});

if (result.canceled === true || result.assets.length === 0) {
return;
}

uploadResume(result.assets[0].uri);
}, [uploadResume]);

return (
<Button
mode='contained-tonal'
onPress={handleUploadResume}
icon='file-upload-outline'
>
{i18n.t('profile.info.uploadResume')}
</Button>
);
};

export default ProfileUploadResumeButton;
8 changes: 2 additions & 6 deletions front/src/components/profile/header/ProfileHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { FC } from 'react';
import { StyleSheet, View } from 'react-native';
import { Button } from 'react-native-paper';

import ProfileShareResumeButton from '@/components/profile/ProfileShareResumeButton';
import ProfileHeaderDescription from '@/components/profile/header/ProfileHeaderDescription';
import ProfileHeaderName from '@/components/profile/header/ProfileHeaderName';
import i18n from '@/utils/i18n';

/**
* The styles for the ProfileHeader component.
Expand Down Expand Up @@ -68,10 +67,7 @@ const ProfileHeader: FC<ProfileHeaderProps> = ({
/>

<ProfileHeaderDescription shortBiography={shortBiography} />

<Button icon='file-document' mode={'contained-tonal'}>
{i18n.t('profile.info.downloadResume')}
</Button>
<ProfileShareResumeButton />
</View>
);
};
Expand Down
10 changes: 10 additions & 0 deletions front/src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
interface FormDataValue {
uri: string;
name: string;
type: string;
}

interface FormData {
append(name: string, value: FormDataValue, fileName?: string): void;
set(name: string, value: FormDataValue, fileName?: string): void;
}
Loading

0 comments on commit 6f1ceb5

Please sign in to comment.