Small web app for sharing your favorite music.
You can either create a recommendation for a song or you can view a random one.
I created this small app with the intention of learning Typescript in a React context.
The backend of the app has been build using Django and Django Rest Framework. Spotipy was used as a wrapper for the spotify API.
urlpatterns = [
path('song/', views.ListTracks.as_view()),
path('recs/', views.RecommandationApiView.as_view()),
path('random/', views.RecommandationApiRandom.as_view()),
path('track/', views.TrackAPI.as_view()),
]
The song/
and track/
endpoints are used for interfacing with the spotify API. song/
is used for finding tracks based on their name while track/
is used for finding a track based on its id.
The rects/
endpoint is used for interacting with my Recommendations API.
The random/
endpoint is used for retrieving a random Recommendation.
Simple API for storing Recommendations
class RecommandationApiView(APIView):
def get(self, request, *args, **kwargs):
"""
List all Recommandations updated in the past day.
"""
saptamana_trecuta = timezone.now() - datetime.timedelta(days=1)
recommands = Recommandation.objects.filter(updated__gte=saptamana_trecuta)
serializer = RecommandationSerializer(recommands, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request, *args, **kwargs):
""" Created a Recommandation with the given data """
data = {
'title': request.data.get('title'),
'description': request.data.get('description'),
'trackId': request.data.get('trackId'),
'updated': str(timezone.now())
}
serializer = RecommandationSerializer(data=data)
if(serializer.is_valid()):
serializer.save()
return Response(serializer.data, status=status.HTTP_202_ACCEPTED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The frontend has been built with Create React App, using React and Typescript.
withTrackId
HOC used for injecting an ID propery into a TrackDisplay
component. It also adds a getId
property for passing a callback.
This callback has that given id as an argument.
export function withTrackId<T extends WithTrackIdProps>(Component: React.ComponentType<Omit<T, keyof WithTrackIdProps>>) {
return class extends React.Component<T> {
constructor(props: T & WithTrackIdProps) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
// Calls the callback.
handleClick() {
this.props.getId(this.props.track_id);
}
render() {
// Separate the component props from the new Hoc props.
const {track_id, getId, ...props} = this.props;
return (
<div onClick={this.handleClick}>
<Component {...props as T} />
</div>
);
}
}
}
A TrackDisplay
component is a component that recieves this as props
/** The props for the TrackDisplay. */
export interface TrackDisplayProps {
/** Name of the song. */
name: string;
/** Name of the artist */
artist: string;
/** The url of the image. */
image_url: string;
}
This a simple example of a stateless TrackDisplay
component
export const TrackDisplay = ({name, artist, image_url}: TrackDisplayProps) => {
return (
<div>
<img src={image_url} alt={name} />
<span><h3>{name}</h3> by <h2>{artist}</h2></span>
</div>
)
}
Here is a more complete example of the HOC usage.
/** A Track Display that gets replaced by a spotify iframe when clicked. */
export class TrackDisplayWithPlayer extends Component<TrackDisplayProps & ExtraProps, TDWPState> {
constructor(props: TrackDisplayProps & ExtraProps){
super(props);
this.state = {wasClicked: false};
this.onClick = this.onClick.bind(this);
}
onClick() {
this.setState({
wasClicked: true,
});
}
componentDidUpdate(prevProps: TrackDisplayProps & ExtraProps) {
if(prevProps.track_id !== this.props.track_id)
this.setState({wasClicked: false});
}
render() {
const TrackDisplayWithId = withTrackId<TrackDisplayProps & WithTrackIdProps>(TrackDisplayForList);
const {track_id, ...other_props} = this.props; // Spreading props
return(
<div>
{!this.state.wasClicked ? <TrackDisplayWithId {...other_props}
track_id={''} getId={this.onClick}/> : <GenericPlayer id={track_id} type='track'/>}
</div>
);
}
}
The design aspect of the app has been created using material-ui.
Here are some images of the app.
---- | ---- |
---- | ---- |