implement profile photo upload
This commit is contained in:
parent
9668b59e23
commit
3175f77b7f
|
|
@ -4,59 +4,65 @@ import { DropdownItem, DropdownMenu, DropdownToggle, Dropdown } from 'reactstrap
|
|||
import { useAuth } from '../../context/UserAuth';
|
||||
import JKProfileAvatar from '../profile/JKProfileAvatar';
|
||||
import { useCookies } from 'react-cookie';
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
// import useUserProfile from '../../hooks/useUserProfile';
|
||||
import { useAppData } from '../../context/AppDataContext';
|
||||
|
||||
const ProfileDropdown = () => {
|
||||
const { t } = useTranslation();
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const toggle = () => setDropdownOpen(prevState => !prevState);
|
||||
const { isAuthenticated, currentUser, setCurrentUser, logout } = useAuth();
|
||||
const { isAuthenticated, currentUser, setCurrentUser, logout, currentUserProfile } = useAuth();
|
||||
const [cookies, setCookie, removeCookie] = useCookies(['remember_token']);
|
||||
const history = useHistory();
|
||||
|
||||
const handleLogout = async (event) => {
|
||||
// const { photoUrl } = useUserProfile(currentUser);
|
||||
const { appData } = useAppData();
|
||||
const { currentUserPhotoUrl } = appData;
|
||||
|
||||
const handleLogout = async event => {
|
||||
event.preventDefault();
|
||||
removeCookie('remember_token', {
|
||||
domain: `.${process.env.REACT_APP_ORIGIN}`
|
||||
});
|
||||
setCurrentUser(null);
|
||||
await logout()
|
||||
history.push('/')
|
||||
await logout();
|
||||
history.push('/');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isAuthenticated &&
|
||||
<Dropdown
|
||||
nav
|
||||
inNavbar
|
||||
data-testid="navbarTopProfileDropdown"
|
||||
isOpen={dropdownOpen}
|
||||
toggle={toggle}
|
||||
// onMouseOver={() => {
|
||||
// let windowWidth = window.innerWidth;
|
||||
// windowWidth > 992 && setDropdownOpen(true);
|
||||
// }}
|
||||
// onMouseLeave={() => {
|
||||
// let windowWidth = window.innerWidth;
|
||||
// windowWidth > 992 && setDropdownOpen(false);
|
||||
// }}
|
||||
>
|
||||
<DropdownToggle nav className="pr-0">
|
||||
<JKProfileAvatar src={currentUser.photo_url} className="d-block d-lg-none d-xl-none" />
|
||||
{/* <span className="d-none d-lg-block">{currentUser && currentUser.name}</span> */}
|
||||
</DropdownToggle>
|
||||
<DropdownMenu right className="dropdown-menu-card">
|
||||
<div className="bg-white rounded-soft py-2">
|
||||
{/* <DropdownItem tag={Link} to="/pages/settings">
|
||||
{isAuthenticated && (
|
||||
<Dropdown
|
||||
nav
|
||||
inNavbar
|
||||
data-testid="navbarTopProfileDropdown"
|
||||
isOpen={dropdownOpen}
|
||||
toggle={toggle}
|
||||
// onMouseOver={() => {
|
||||
// let windowWidth = window.innerWidth;
|
||||
// windowWidth > 992 && setDropdownOpen(true);
|
||||
// }}
|
||||
// onMouseLeave={() => {
|
||||
// let windowWidth = window.innerWidth;
|
||||
// windowWidth > 992 && setDropdownOpen(false);
|
||||
// }}
|
||||
>
|
||||
<DropdownToggle nav className="pr-0">
|
||||
{<JKProfileAvatar src={currentUserPhotoUrl} className="d-none d-lg-block d-xl-block" />}
|
||||
{/* <span className="d-none d-lg-block">{currentUser && currentUser.name}</span> */}
|
||||
</DropdownToggle>
|
||||
<DropdownMenu right className="dropdown-menu-card">
|
||||
<div className="bg-white rounded-soft py-2">
|
||||
{/* <DropdownItem tag={Link} to="/pages/settings">
|
||||
My Profile
|
||||
</DropdownItem> */}
|
||||
<DropdownItem onClick={handleLogout}>{t('signout', { ns: 'auth' })}</DropdownItem>
|
||||
</div>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
<DropdownItem onClick={handleLogout}>{t('signout', { ns: 'auth' })}</DropdownItem>
|
||||
</div>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useRef, useEffect, useLayoutEffect, useState, useReducer } from 'react';
|
||||
import { Card, CardBody, Col, Row, CardHeader, Form, FormGroup, Label, Input, Button } from 'reactstrap';
|
||||
import { Card, CardBody, Col, Row, CardHeader, Form, FormGroup, Label, Input } from 'reactstrap';
|
||||
import Select from 'react-select';
|
||||
import FalconCardHeader from '../common/FalconCardHeader';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
|
@ -7,7 +7,6 @@ import JKProfileAvatar from '../profile/JKProfileAvatar';
|
|||
import { useAuth } from '../../context/UserAuth';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import {
|
||||
getPersonById,
|
||||
getInstruments,
|
||||
getGenres,
|
||||
updateUser,
|
||||
|
|
@ -18,6 +17,8 @@ import {
|
|||
import JKProfileAvatarUpload from '../profile/JKProfileAvatarUpload';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Prompt } from 'react-router';
|
||||
// import useUserProfile from '../../hooks/useUserProfile';
|
||||
import { useAppData } from '../../context/AppDataContext';
|
||||
|
||||
function JKEditProfile() {
|
||||
const { t } = useTranslation('profile');
|
||||
|
|
@ -25,7 +26,6 @@ function JKEditProfile() {
|
|||
const [musicInstruments, setMusicInstruments] = useState([]);
|
||||
const [genres, setGenres] = useState([]);
|
||||
const [instrumentsInitialLoadingDone, setInstrumentsInitialLoadingDone] = useState(false);
|
||||
const [currentUserLoaded, setCurrentUserLoaded] = useState(false);
|
||||
const [genreInitialLoadingDone, setGenreInitialLoadingDone] = useState(false);
|
||||
const [countries, setCountries] = useState([]);
|
||||
const [regions, setRegions] = useState([]);
|
||||
|
|
@ -33,6 +33,10 @@ function JKEditProfile() {
|
|||
const [showAvatarUpload, setShowAvatarUpload] = useState(false);
|
||||
const [updating, setUpdating] = useState(false);
|
||||
|
||||
// const { userProfile, photoUrl } = useUserProfile(currentUser);
|
||||
const { appData } = useAppData();
|
||||
const { currentUserPhotoUrl, userProfile } = appData;
|
||||
|
||||
const [_, forceUpdate] = useReducer(x => x + 1, 0);
|
||||
|
||||
const saveTimeoutRef = useRef(null);
|
||||
|
|
@ -62,35 +66,18 @@ function JKEditProfile() {
|
|||
}
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (currentUser && !currentUserLoaded) {
|
||||
setCurrentUserLoaded(true);
|
||||
fetchCurentUser().then(data => {
|
||||
console.log('userData', data);
|
||||
updateUserData(data);
|
||||
fetchInstruments();
|
||||
fetchGenres();
|
||||
fetchCountries();
|
||||
});
|
||||
}
|
||||
}, [currentUser]);
|
||||
useEffect(() => {
|
||||
if (!userProfile) return;
|
||||
updateFormData(userProfile);
|
||||
}, [userProfile]);
|
||||
|
||||
const fetchCurentUser = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
getPersonById(currentUser.id)
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
} else {
|
||||
reject('Error fetching user data');
|
||||
}
|
||||
})
|
||||
.then(data => resolve(data))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
fetchInstruments();
|
||||
fetchGenres();
|
||||
fetchCountries();
|
||||
}, []);
|
||||
|
||||
const updateUserData = data => {
|
||||
const updateFormData = data => {
|
||||
setValue('firstName', data.first_name);
|
||||
setValue('lastName', data.last_name);
|
||||
setValue('country', data.country ? data.country : '');
|
||||
|
|
@ -221,7 +208,6 @@ function JKEditProfile() {
|
|||
const onSubmit = data => console.log(data);
|
||||
|
||||
const handleInstrumentSelect = (e, musicInstrument) => {
|
||||
//alert(e.target.checked)
|
||||
if (e.target.checked) {
|
||||
const userInstruments = getValues('instruments');
|
||||
const thisInstrument = userInstruments.find(
|
||||
|
|
@ -481,7 +467,7 @@ function JKEditProfile() {
|
|||
>
|
||||
<div className="d-flex align-items-center">
|
||||
<div>
|
||||
<JKProfileAvatar src={currentUser.photo_url} size="3xl" />
|
||||
{ <JKProfileAvatar src={currentUserPhotoUrl} size="3xl" /> }
|
||||
</div>
|
||||
<div>
|
||||
<FontAwesomeIcon icon={['fas', 'edit']} className="ml-2 mr-1" />
|
||||
|
|
|
|||
|
|
@ -5,30 +5,19 @@ import { useTranslation } from 'react-i18next';
|
|||
import JKModalDialog from '../common/JKModalDialog';
|
||||
import JKProfileAvatar from './JKProfileAvatar';
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
import { getUserDetail, updateAvatar } from '../../helpers/rest';
|
||||
import { deleteAvatar, updateAvatar } from '../../helpers/rest';
|
||||
import { toast } from 'react-toastify';
|
||||
//import useUserProfile from '../../hooks/useUserProfile';
|
||||
import { useAppData } from '../../context/AppDataContext';
|
||||
import { set } from 'react-hook-form';
|
||||
|
||||
const JKProfileAvatarUpload = ({ show, toggle }) => {
|
||||
const { t } = useTranslation('profile');
|
||||
const { currentUser } = useAuth();
|
||||
const [userDetails, setUserDetails] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUser) {
|
||||
console.log('DEBUG', currentUser.id);
|
||||
getUserDetail({ id: currentUser.id })
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
setUserDetails(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
}, [currentUser]);
|
||||
// const { photoUrl } = useUserProfile(currentUser);
|
||||
const { appData, setAppData } = useAppData();
|
||||
const { currentUserPhotoUrl } = appData;
|
||||
const [isProcessing, setIsProsessing] = useState(false);
|
||||
|
||||
const openFilePicker = () => {
|
||||
const client = filestack.init(window.gon.fp_apikey);
|
||||
|
|
@ -45,7 +34,13 @@ const JKProfileAvatarUpload = ({ show, toggle }) => {
|
|||
},
|
||||
storeTo: {
|
||||
location: 's3',
|
||||
path: `${window.gon.fp_upload_dir}/users/${currentUser.id}/`
|
||||
path: `${window.gon.fp_upload_dir}/${currentUser.id}/`
|
||||
},
|
||||
onFileUploadStarted: () => {
|
||||
setIsProsessing(true);
|
||||
},
|
||||
onFileUploadFailed: () => {
|
||||
setIsProsessing(false);
|
||||
},
|
||||
onUploadDone: res => {
|
||||
console.log('onUploadDone', res);
|
||||
|
|
@ -54,46 +49,70 @@ const JKProfileAvatarUpload = ({ show, toggle }) => {
|
|||
if (res.filesUploaded.length > 0) {
|
||||
const opts = {
|
||||
id: currentUser.id,
|
||||
original_fpfile: userDetails.original_fpfile ? userDetails.original_fpfile : null,
|
||||
cropped_fpfile: res.filesUploaded[0].url,
|
||||
cropped_large_fpfile: null,
|
||||
cropped_selection: null,
|
||||
}
|
||||
if(res.filesUploaded[0].cropped){
|
||||
opts['crop_selection'] = {
|
||||
x: res.filesUploaded[0].cropped.cropArea.position[0],
|
||||
y: res.filesUploaded[0].cropped.cropArea.position[1],
|
||||
w: res.filesUploaded[0].cropped.cropArea.size[0],
|
||||
h: res.filesUploaded[0].cropped.cropArea.size[1],
|
||||
};
|
||||
url: res.filesUploaded[0].url,
|
||||
// cropped_selection: null,
|
||||
}
|
||||
// if(res.filesUploaded[0].cropped){
|
||||
// opts['crop_selection'] = {
|
||||
// x: res.filesUploaded[0].cropped.cropArea.position[0],
|
||||
// y: res.filesUploaded[0].cropped.cropArea.position[1],
|
||||
// w: res.filesUploaded[0].cropped.cropArea.size[0],
|
||||
// h: res.filesUploaded[0].cropped.cropArea.size[1],
|
||||
// };
|
||||
// }
|
||||
updateAvatar(opts).then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
}).then(data => {
|
||||
alert('Photo updated successfully');
|
||||
console.log('DEBUG', data);
|
||||
console.log('photo upload success', data);
|
||||
setAppData({
|
||||
...appData,
|
||||
currentUserPhotoUrl: data.v2_photo_url
|
||||
});
|
||||
toast.success('Success! Your avatar has been updated.');
|
||||
setIsProsessing(false);
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
toast.error('An error encountered when updating avatar.');
|
||||
setIsProsessing(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
client.picker(options).open();
|
||||
};
|
||||
|
||||
const deleteProfileAvatar = () => {
|
||||
setIsProsessing(true);
|
||||
deleteAvatar(currentUser.id).then(response => {
|
||||
if (response.ok) {
|
||||
setAppData({
|
||||
...appData,
|
||||
currentUserPhotoUrl: null
|
||||
});
|
||||
toast.success('Success! Your avatar has been deleted.');
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
toast.error('An error encountered when deleting avatar.');
|
||||
}).finally(() => {
|
||||
setIsProsessing(false);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<JKModalDialog show={show} onToggle={toggle} title={t('photo_modal.title', { ns: 'profile' })} showFooter={true}>
|
||||
<div className="d-flex flex-column">
|
||||
<div className="d-flex justify-content-center">
|
||||
<JKProfileAvatar src={currentUser.photo_url} size="5xl" />
|
||||
{ <JKProfileAvatar src={currentUserPhotoUrl} size="5xl" /> }
|
||||
</div>
|
||||
|
||||
|
||||
<div className="d-flex justify-content-center mt-2">
|
||||
<Button color="primary" className="ml-2" onClick={openFilePicker}>
|
||||
<Button color="primary" className="ml-2" onClick={openFilePicker} disabled={isProcessing}>
|
||||
{t('photo_modal.upload', { ns: 'profile' })}
|
||||
</Button>
|
||||
<Button color="secondary" outline className="ml-2" onClick={() => {}}>
|
||||
<Button color="secondary" outline className="ml-2" onClick={deleteProfileAvatar} disabled={isProcessing}>
|
||||
{t('photo_modal.delete', { ns: 'profile' })}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import React from 'react';
|
||||
import useUserProfile from '../hooks/useUserProfile';
|
||||
import { useAuth } from './UserAuth';
|
||||
|
||||
// AppDataContext.js
|
||||
// this context is used to store app data that is shared across the app
|
||||
const AppDataContext = React.createContext(null);
|
||||
|
||||
export const AppDataProvider = ({ children }) => {
|
||||
const [appData, setAppData] = React.useState({});
|
||||
const { currentUser } = useAuth();
|
||||
const { userProfile, photoUrl } = useUserProfile(currentUser);
|
||||
|
||||
React.useEffect(() => {
|
||||
setAppData({ userProfile, currentUserPhotoUrl: photoUrl });
|
||||
}, [currentUser, userProfile, photoUrl]);
|
||||
|
||||
return <AppDataContext.Provider value={{ appData, setAppData }}>{children}</AppDataContext.Provider>;
|
||||
};
|
||||
|
||||
export const useAppData = () => React.useContext(AppDataContext);
|
||||
|
|
@ -1,32 +1,31 @@
|
|||
import React, { useState, useEffect, createContext, useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { checkIsAuthenticated, authSignUp, authLogin, authLogout } from '../services/auth'
|
||||
import { checkIsAuthenticated, authSignUp, authLogin, authLogout } from '../services/auth';
|
||||
|
||||
export const UserAuthContext = createContext({});
|
||||
|
||||
export const useAuth = () => useContext(UserAuthContext)
|
||||
export const useAuth = () => useContext(UserAuthContext);
|
||||
|
||||
export default function UserAuth({ children, path }) {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const [currentUser, setCurrentUser] = useState(null)
|
||||
const [currentUser, setCurrentUser] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
//console.log('checking auth for', path);
|
||||
checkAuth();
|
||||
}, [path]);
|
||||
|
||||
const checkAuth = () =>
|
||||
checkIsAuthenticated()
|
||||
.then(resp => resp.json())
|
||||
.then((user) => {
|
||||
.then(user => {
|
||||
window.currentUser = user;
|
||||
setCurrentUser(user)
|
||||
setIsAuthenticated(true)
|
||||
setCurrentUser(user);
|
||||
setIsAuthenticated(true);
|
||||
})
|
||||
.catch(() => {
|
||||
setIsAuthenticated(false)
|
||||
setCurrentUser(null)
|
||||
setIsAuthenticated(false);
|
||||
setCurrentUser(null);
|
||||
window.currentUser = null;
|
||||
})
|
||||
.then(() => setIsLoading(false));
|
||||
|
|
@ -40,7 +39,7 @@ export default function UserAuth({ children, path }) {
|
|||
});
|
||||
|
||||
const logout = () => {
|
||||
authLogout()
|
||||
authLogout();
|
||||
setIsAuthenticated(false);
|
||||
};
|
||||
|
||||
|
|
@ -53,7 +52,9 @@ export default function UserAuth({ children, path }) {
|
|||
});
|
||||
|
||||
return (
|
||||
<UserAuthContext.Provider value={{ currentUser, setCurrentUser, isAuthenticated, isLoading, login, logout, signUp }}>
|
||||
<UserAuthContext.Provider
|
||||
value={{ currentUser, setCurrentUser, isAuthenticated, isLoading, login, logout, signUp }}
|
||||
>
|
||||
{children}
|
||||
</UserAuthContext.Provider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -546,17 +546,25 @@ export const deleteMixdown = id => {
|
|||
export const updateAvatar = options => {
|
||||
const { id, ...rest } = options;
|
||||
const opts = {
|
||||
original_fpfile: rest['original_fpfile'],
|
||||
cropped_fpfile: rest['cropped_fpfile'],
|
||||
cropped_large_fpfile: rest['cropped_large_fpfile'],
|
||||
crop_selection: rest['crop_selection']
|
||||
url: rest['url'],
|
||||
// crop_selection: rest['crop_selection']
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/users/${id}/avatar`, {
|
||||
apiFetch(`/users/${id}/avatar_v2`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(opts)
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
export const deleteAvatar = id => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/users/${id}/avatar_v2`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import { getPersonById } from '../helpers/rest';
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
|
||||
const useUserProfile = (user) => {
|
||||
const [userProfile, setUserProfile] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
setUserProfile(null);
|
||||
return;
|
||||
}
|
||||
getPersonById(user.id)
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
setUserProfile(data)
|
||||
})
|
||||
.catch(error => console.error(error));
|
||||
|
||||
return () => {
|
||||
setUserProfile(null);
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
|
||||
const photoUrl = useMemo(() => {
|
||||
if(userProfile && userProfile.v2_photo_uploaded){
|
||||
return userProfile.v2_photo_url
|
||||
}else if(userProfile && !userProfile.v2_photo_uploaded){
|
||||
return user.photo_url
|
||||
}
|
||||
return null
|
||||
}, [userProfile])
|
||||
|
||||
return{
|
||||
userProfile,
|
||||
photoUrl
|
||||
}
|
||||
}
|
||||
|
||||
export default useUserProfile;
|
||||
|
|
@ -4,12 +4,10 @@ import PropTypes from 'prop-types';
|
|||
import DashboardMain from '../components/dashboard/JKDashboardMain';
|
||||
import UserAuth from '../context/UserAuth';
|
||||
import { BrowserQueryProvider } from '../context/BrowserQuery';
|
||||
|
||||
import { NativeAppProvider } from '../context/NativeAppContext';
|
||||
|
||||
import { JKLobbyChatProvider } from '../components/sessions/JKLobbyChatContext';
|
||||
|
||||
import { AppRoutesProvider } from '../context/AppRoutesContext';
|
||||
import { AppDataProvider } from '../context/AppDataContext';
|
||||
|
||||
const DashboardLayout = ({ location }) => {
|
||||
useEffect(() => {
|
||||
|
|
@ -19,13 +17,15 @@ const DashboardLayout = ({ location }) => {
|
|||
return (
|
||||
<UserAuth path={location.pathname}>
|
||||
<AppRoutesProvider>
|
||||
<BrowserQueryProvider>
|
||||
<NativeAppProvider>
|
||||
<JKLobbyChatProvider>
|
||||
<DashboardMain />
|
||||
</JKLobbyChatProvider>
|
||||
</NativeAppProvider>
|
||||
</BrowserQueryProvider>
|
||||
<AppDataProvider>
|
||||
<BrowserQueryProvider>
|
||||
<NativeAppProvider>
|
||||
<JKLobbyChatProvider>
|
||||
<DashboardMain />
|
||||
</JKLobbyChatProvider>
|
||||
</NativeAppProvider>
|
||||
</BrowserQueryProvider>
|
||||
</AppDataProvider>
|
||||
</AppRoutesProvider>
|
||||
</UserAuth>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
class AddV2PhotoAttributes < ActiveRecord::Migration
|
||||
def self.up
|
||||
execute("ALTER TABLE public.users ADD COLUMN v2_photo_url VARCHAR(2048); ALTER TABLE public.users ADD COLUMN v2_photo_uploaded BOOLEAN; UPDATE public.users SET v2_photo_uploaded = FALSE; ALTER TABLE public.users ALTER COLUMN v2_photo_uploaded SET DEFAULT FALSE;")
|
||||
end
|
||||
def self.down
|
||||
execute("ALTER TABLE public.users DROP COLUMN v2_photo_url; ALTER TABLE public.users DROP COLUMN v2_photo_uploaded;")
|
||||
end
|
||||
end
|
||||
|
|
@ -73,7 +73,8 @@ module JamRuby
|
|||
|
||||
after_save :update_teacher_pct
|
||||
|
||||
attr_accessible :first_name, :last_name, :email, :city, :password, :password_confirmation, :state, :country, :birth_date, :subscribe_email, :terms_of_service, :original_fpfile, :cropped_fpfile, :cropped_large_fpfile, :cropped_s3_path, :cropped_large_s3_path, :photo_url, :large_photo_url, :crop_selection, :used_current_month, :used_month_play_time
|
||||
attr_accessible :first_name, :last_name, :email, :city, :password, :password_confirmation, :state, :country, :birth_date, :subscribe_email, :terms_of_service, :original_fpfile, :cropped_fpfile, :cropped_large_fpfile, :cropped_s3_path, :cropped_large_s3_path, :photo_url, :large_photo_url, :crop_selection, :used_current_month, :used_month_play_time,
|
||||
:v2_photo_url, :v2_photo_uploaded
|
||||
|
||||
# updating_password corresponds to a lost_password
|
||||
attr_accessor :test_drive_packaging, :validate_instruments, :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field, :mods_json, :expecting_gift_card, :purchase_required, :user_type
|
||||
|
|
@ -295,7 +296,7 @@ module JamRuby
|
|||
validate :validate_musician_instruments, if: :validate_instruments
|
||||
validate :validate_current_password
|
||||
validate :validate_update_email
|
||||
validate :validate_avatar_info
|
||||
validate :validate_avatar_info, unless: :avatar_v2_available?
|
||||
validate :email_case_insensitive_uniqueness
|
||||
validate :validate_spammy_names
|
||||
validate :update_email_case_insensitive_uniqueness, :if => :updating_email
|
||||
|
|
@ -671,6 +672,10 @@ module JamRuby
|
|||
end
|
||||
end
|
||||
|
||||
def avatar_v2_available?
|
||||
self.v2_photo_url.present?
|
||||
end
|
||||
|
||||
def email_case_insensitive_uniqueness
|
||||
# using the case insensitive unique check of active record will downcase the field, which is not what we want--we want to preserve original casing
|
||||
search = User.where("email ILIKE ?", self.email).first
|
||||
|
|
@ -2054,6 +2059,13 @@ module JamRuby
|
|||
)
|
||||
end
|
||||
|
||||
def update_avatar_v2(url)
|
||||
self.update_attributes(
|
||||
:v2_photo_url => url,
|
||||
:v2_photo_uploaded => true
|
||||
)
|
||||
end
|
||||
|
||||
def delete_avatar(aws_bucket)
|
||||
|
||||
User.transaction do
|
||||
|
|
@ -2078,6 +2090,19 @@ module JamRuby
|
|||
|
||||
end
|
||||
|
||||
def delete_avatar_v2(aws_bucket)
|
||||
User.transaction do
|
||||
#TODO: delete the v2_photo_url from s3
|
||||
# unless self.v2_photo_url.nil?
|
||||
# S3Util.delete(aws_bucket, self.v2_photo_url)
|
||||
# end
|
||||
|
||||
return self.update_attributes(
|
||||
:v2_photo_url => nil
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# throws RecordNotFound if signup token is invalid; i.e., if it's nil, empty string, or not belonging to a user
|
||||
def self.signup_confirm(signup_token)
|
||||
if signup_token.nil? || signup_token.empty?
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class ApiUsersController < ApiController
|
|||
:friend_show, :friend_destroy, # friends
|
||||
:notification_index, :notification_destroy, # notifications
|
||||
:band_invitation_index, :band_invitation_show, :band_invitation_update, # band invitations
|
||||
:set_password, :begin_update_email, :update_avatar, :delete_avatar, :generate_filepicker_policy, :request_reset_password,
|
||||
:set_password, :begin_update_email, :update_avatar, :update_avatar_v2, :delete_avatar, :delete_avatar_v2, :generate_filepicker_policy, :request_reset_password,
|
||||
:share_session, :share_recording,
|
||||
:affiliate_report, :audio_latency, :get_latencies, :broadcast_notification, :redeem_giftcard, :post_app_interactions]
|
||||
|
||||
|
|
@ -626,8 +626,6 @@ class ApiUsersController < ApiController
|
|||
cropped_large_fpfile = params[:cropped_large_fpfile]
|
||||
crop_selection = params[:crop_selection]
|
||||
|
||||
debugger
|
||||
|
||||
# public bucket to allow images to be available to public
|
||||
@user.update_avatar(original_fpfile, cropped_fpfile, cropped_large_fpfile, crop_selection, Rails.application.config.aws_bucket_public)
|
||||
|
||||
|
|
@ -638,6 +636,19 @@ class ApiUsersController < ApiController
|
|||
end
|
||||
end
|
||||
|
||||
def update_avatar_v2
|
||||
url = params[:url]
|
||||
# crop_selection = params[:crop_selection]
|
||||
|
||||
@user.update_avatar_v2(url)
|
||||
|
||||
if @user.errors.any?
|
||||
respond_with @user, status: :unprocessable_entity
|
||||
else
|
||||
respond_with @user, responder: ApiResponder, status: 200
|
||||
end
|
||||
end
|
||||
|
||||
def delete_avatar
|
||||
@user.delete_avatar(Rails.application.config.aws_bucket_public)
|
||||
|
||||
|
|
@ -648,6 +659,16 @@ class ApiUsersController < ApiController
|
|||
end
|
||||
end
|
||||
|
||||
def delete_avatar_v2
|
||||
@user.delete_avatar_v2(Rails.application.config.aws_bucket_public)
|
||||
|
||||
if @user.errors.any?
|
||||
respond_with @user, status: :unprocessable_entity
|
||||
else
|
||||
respond_with @user, responder: ApiResponder, status: 204
|
||||
end
|
||||
end
|
||||
|
||||
def generate_filepicker_policy
|
||||
# generates a soon-expiring filepicker policy so that a user can only upload to their own folder in their bucket
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
object @profile
|
||||
|
||||
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count, :recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count, :age, :website, :skill_level, :concert_count, :studio_session_count, :virtual_band, :virtual_band_commitment, :traditional_band, :traditional_band_commitment, :traditional_band_touring, :paid_sessions, :paid_sessions_hourly_rate,
|
||||
:paid_sessions_daily_rate, :free_sessions, :cowriting, :cowriting_purpose, :subscribe_email, :is_a_teacher, :is_a_student, :last_active_timestamp
|
||||
:paid_sessions_daily_rate, :free_sessions, :cowriting, :cowriting_purpose, :subscribe_email, :is_a_teacher, :is_a_student, :last_active_timestamp, :v2_photo_url, :v2_photo_uploaded
|
||||
|
||||
child :online_presences => :online_presences do
|
||||
attributes :id, :service_type, :username
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ object @user
|
|||
|
||||
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :online, :photo_url, :musician, :gender, :birth_date, :internet_service_provider, :friend_count, :liker_count, :like_count, :follower_count, :following_count, :admin,
|
||||
:recording_count, :session_count, :biography, :favorite_count, :audio_latency, :upcoming_session_count, :age, :website, :skill_level, :reuse_card, :email_needs_verification, :is_a_teacher, :is_a_student, :is_onboarder, :timezone,
|
||||
:use_video_conferencing_server
|
||||
:use_video_conferencing_server, :v2_photo_url, :v2_photo_uploaded
|
||||
|
||||
node :location do |user|
|
||||
if user.musician?
|
||||
|
|
|
|||
|
|
@ -509,6 +509,9 @@ Rails.application.routes.draw do
|
|||
match '/users/:id/avatar' => 'api_users#delete_avatar', :via => :delete
|
||||
match '/users/:id/filepicker_policy' => 'api_users#generate_filepicker_policy', :via => :get
|
||||
|
||||
match '/users/:id/avatar_v2' => 'api_users#update_avatar_v2', :via => :post
|
||||
match '/users/:id/avatar_v2' => 'api_users#delete_avatar_v2', :via => :delete
|
||||
|
||||
# user progression
|
||||
match '/users/progression/downloaded_client' => 'api_users#downloaded_client', :via => :post
|
||||
match '/users/progression/certified_gear' => 'api_users#qualified_gear', :via => :post
|
||||
|
|
|
|||
Loading…
Reference in New Issue