Use axios base url.

This commit is contained in:
SamTV12345 2024-02-14 22:06:52 +01:00
parent 1e3d62afab
commit b90deb76ad
41 changed files with 754 additions and 143 deletions

View File

@ -53,11 +53,13 @@
"@vite-pwa/assets-generator": "^0.2.3",
"@vitejs/plugin-react-swc": "^3.6.0",
"autoprefixer": "^10.4.17",
"jsdom": "^24.0.0",
"postcss": "^8.4.35",
"sass": "^1.70.0",
"tailwindcss": "^3.4.1",
"typescript": "^5.3.3",
"vite": "^5.1.1",
"vite-plugin-pwa": "^0.18.1"
"vite-plugin-pwa": "^0.18.1",
"vitest": "^1.2.2"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ import axios, { AxiosResponse } from 'axios'
import { enqueueSnackbar } from 'notistack'
import useCommon, {LoggedInUser} from './store/CommonSlice'
import useOpmlImport from './store/opmlImportSlice'
import {apiURL, configWSUrl, decodeHTMLEntities, isJsonString} from './utils/Utilities'
import {decodeHTMLEntities, isJsonString} from './utils/Utilities'
import {
UserAdminViewLazyLoad,
EpisodeSearchViewLazyLoad,
@ -39,6 +39,7 @@ import {SettingsPodcastDelete} from "./components/SettingsPodcastDelete";
import {UserAdminUsers} from "./components/UserAdminUsers";
import {UserAdminInvites} from "./components/UserAdminInvites";
import {UserManagementPage} from "./pages/UserManagement";
import {configWSUrl} from "./utils/navigationUtils";
export const router = createBrowserRouter(createRoutesFromElements(
<>
@ -196,14 +197,14 @@ const App: FC<PropsWithChildren> = ({ children }) => {
useEffect(() => {
if (config?.basicAuth||config?.oidcConfigured||config?.reverseProxy){
axios.get(apiURL + '/users/me')
axios.get('/users/me')
.then((c:AxiosResponse<LoggedInUser>)=>useCommon.getState().setLoggedInUser(c.data))
.catch(() => enqueueSnackbar(t('not-admin'), { variant: 'error' }))
}
}, []);
const getNotifications = () => {
axios.get(apiURL + '/notifications/unread')
axios.get( '/notifications/unread')
.then((response: AxiosResponse<Notification[]>) => {
setNotifications(response.data)
})

View File

@ -2,7 +2,6 @@ import { useTranslation } from 'react-i18next'
import axios from 'axios'
import { enqueueSnackbar } from 'notistack'
import useCommon from '../store/CommonSlice'
import { apiURL } from '../utils/Utilities'
import { User } from '../models/User'
import { CustomButtonPrimary } from './CustomButtonPrimary'
import { CustomSelect } from './CustomSelect'
@ -26,7 +25,7 @@ export const ChangeRoleModal = () => {
//setSelectedUser, setUsers
const changeRole = () => {
axios.put(apiURL + '/users/' + selectedUser?.username + '/role', {
axios.put( '/users/' + selectedUser?.username + '/role', {
role: selectedUser?.role,
explicitConsent: selectedUser?.explicitConsent
})

View File

@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
import axios from 'axios'
import { enqueueSnackbar } from 'notistack'
import useCommon from '../store/CommonSlice'
import { apiURL } from '../utils/Utilities'
import { CustomButtonPrimary } from './CustomButtonPrimary'
import { CustomSelect } from './CustomSelect'
import { Heading2 } from './Heading2'
@ -61,7 +60,7 @@ export const CreateInviteModal = () => {
role: invite.role.toLowerCase(),
explicitConsent: invite.explicitConsent
} satisfies Invite
axios.post(apiURL + '/users/invites', modifiedInvite)
axios.post( '/users/invites', modifiedInvite)
.then((v) => {
enqueueSnackbar(t('invite-created'), { variant: 'success' })
setInvites([...invites,v.data])

View File

@ -3,7 +3,6 @@ import {createPortal} from "react-dom"
import {useTranslation} from "react-i18next"
import axios, {AxiosResponse} from "axios"
import {enqueueSnackbar} from "notistack"
import {apiURL} from "../utils/Utilities"
import {Heading2} from "./Heading2"
import "material-symbols/outlined.css"
import {PlaylistDto, PlaylistDtoPost, PlaylistDtoPut, PlaylistItem} from "../models/Playlist";
@ -37,7 +36,7 @@ export const CreatePlaylistModal = () => {
if (currentPlaylistToEdit && currentPlaylistToEdit.id !== -1){
axios.put(apiURL+"/playlist/"+currentPlaylistToEdit.id, {
axios.put("/playlist/"+currentPlaylistToEdit.id, {
id: currentPlaylistToEdit.id,
name: currentPlaylistToEdit.name,
items: itemsMappedToIDs
@ -50,7 +49,7 @@ export const CreatePlaylistModal = () => {
}
if (currentPlaylistToEdit && currentPlaylistToEdit.id === -1) {
axios.post(apiURL + "/playlist", {
axios.post( "/playlist", {
name: currentPlaylistToEdit.name,
items: itemsMappedToIDs
} satisfies PlaylistDtoPost)

View File

@ -1,6 +1,6 @@
import { FC} from 'react'
import axios, { AxiosResponse } from 'axios'
import { apiURL, preparePath } from '../utils/Utilities'
import {preparePath } from '../utils/Utilities'
import {Podcast, PodcastEpisode} from '../store/CommonSlice'
import { PodcastWatchedEpisodeModel } from '../models/PodcastWatchedEpisodeModel'
import {Episode} from "../models/Episode";
@ -41,7 +41,7 @@ export const EpisodeCard: FC<EpisodeCardProps> = ({ podcast, podcastEpisode, tot
return (
<div className="group cursor-pointer" key={podcastEpisode.episode_id+"dv"} onClick={()=>{
axios.get(apiURL + '/podcast/episode/' + podcastEpisode.episode_id)
axios.get( '/podcast/episode/' + podcastEpisode.episode_id)
.then((response: AxiosResponse<Episode>) => {
handlePlayofEpisode(response, {
podcastEpisode: podcastEpisode,

View File

@ -1,9 +1,8 @@
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import axios, { AxiosResponse } from 'axios'
import { PodcastEpisode } from '../store/CommonSlice'
import {apiURL, formatTime, prependAPIKeyOnAuthEnabled, removeHTML} from '../utils/Utilities'
import {formatTime, prependAPIKeyOnAuthEnabled, removeHTML} from '../utils/Utilities'
import { useDebounce } from '../utils/useDebounce'
import { CustomInput } from './CustomInput'
import { Spinner } from './Spinner'
@ -28,7 +27,7 @@ export const EpisodeSearch: FC<EpisodeSearchProps> = ({ classNameResults = '', o
if (searchName.trim().length > 0) {
setSearching(true)
axios.get(apiURL + '/podcasts/' + searchName + '/query')
axios.get( '/podcasts/' + searchName + '/query')
.then((v: AxiosResponse<PodcastEpisode[]>) => {
setSearchResults(v.data)
setSearching(false)

View File

@ -2,7 +2,6 @@ import { FC } from 'react'
import { useForm } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import axios, { AxiosResponse } from 'axios'
import { apiURL } from '../utils/Utilities'
import { handleAddPodcast } from '../utils/ErrorSnackBarResponses'
import { Podcast } from '../store/CommonSlice'
import { CustomButtonPrimary } from './CustomButtonPrimary'
@ -23,7 +22,7 @@ export const FeedURLComponent: FC = () => {
const feedUrlWatched = watch('feedUrl')
const onSubmit = (data: FeedURLFormData) => {
axios.post(apiURL + '/podcast/feed', {
axios.post( '/podcast/feed', {
rssFeedUrl: data.feedUrl
}).then((v: AxiosResponse<Podcast>) => {
handleAddPodcast(v.status, v.data.name, t)

View File

@ -2,7 +2,6 @@ import { FC, RefObject, useEffect } from 'react'
import axios, { AxiosResponse } from 'axios'
import useOnMount from '../hooks/useOnMount'
import useAudioPlayer from '../store/AudioPlayerSlice'
import { apiURL } from '../utils/Utilities'
import { AudioAmplifier } from '../models/AudioAmplifier'
import { PodcastWatchedModel } from '../models/PodcastWatchedModel'
@ -23,7 +22,7 @@ export const HiddenAudioPlayer: FC<HiddenAudioPlayerProps> = ({ refItem, setAudi
if (podcastEpisode.time === undefined) {
// fetch time from server
axios.get(apiURL + '/podcast/episode/' + podcastEpisode.episode_id)
axios.get( '/podcast/episode/' + podcastEpisode.episode_id)
.then((response: AxiosResponse<PodcastWatchedModel>) => {
setCurrentPodcastEpisode({
...podcastEpisode,

View File

@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
import axios from 'axios'
import { AnimatePresence, motion } from 'framer-motion'
import * as Popover from '@radix-ui/react-popover'
import {apiURL, removeHTML} from '../utils/Utilities'
import { removeHTML} from '../utils/Utilities'
import useCommon from '../store/CommonSlice'
import { Notification } from '../models/Notification'
import 'material-symbols/outlined.css'
@ -38,7 +38,7 @@ export const Notifications: FC = () => {
)
const dismissNotification = (notification: Notification) => {
axios.put(apiURL + '/notifications/dismiss', { id: notification.id })
axios.put( '/notifications/dismiss', { id: notification.id })
.then(() => {
removeNotification(notification.id)
})

View File

@ -1,7 +1,6 @@
import { FC, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import axios from 'axios'
import { apiURL } from '../utils/Utilities'
import { FileItem, readFile } from '../utils/FileUtils'
import useOpmlImport from '../store/opmlImportSlice'
import { AddTypes } from '../models/AddTypes'
@ -53,7 +52,7 @@ export const OpmlAdd: FC<OpmlAddProps> = ({}) => {
setPodcastsToUpload(count)
axios.post(apiURL + '/podcast/opml', {
axios.post( '/podcast/opml', {
content: files[0].content
})
}

View File

@ -1,6 +1,6 @@
import React, { createRef, FC, useMemo, useState } from 'react'
import useAudioPlayer from '../store/AudioPlayerSlice'
import { logCurrentPlaybackTime } from '../utils/Utilities'
import {logCurrentPlaybackTime} from "../utils/navigationUtils";
type PlayerProgressBarProps = {
audioplayerRef: React.RefObject<HTMLAudioElement>,

View File

@ -1,7 +1,5 @@
import {FC, RefObject, useEffect} from 'react'
import {
apiURL,
logCurrentPlaybackTime,
prepareOnlinePodcastEpisode,
preparePodcastEpisode,
SKIPPED_TIME
@ -13,6 +11,7 @@ import {PodcastWatchedModel} from "../models/PodcastWatchedModel";
import useCommon from "../store/CommonSlice";
import {EpisodesWithOptionalTimeline} from "../models/EpisodesWithOptionalTimeline";
import {Episode} from "../models/Episode";
import {logCurrentPlaybackTime} from "../utils/navigationUtils";
type PlayerTimeControlsProps = {
refItem: RefObject<HTMLAudioElement>
@ -70,7 +69,7 @@ export const PlayerTimeControls: FC<PlayerTimeControlsProps> = ({ refItem }) =>
if (refItem === undefined || refItem.current === undefined|| refItem.current === null) return
const nextEpisode = episodes[index].podcastEpisode
axios.get(apiURL + "/podcast/episode/" + nextEpisode.episode_id)
axios.get( "/podcast/episode/" + nextEpisode.episode_id)
.then((response: AxiosResponse<Episode>) => {
setCurrentPodcastEpisode(nextEpisode)
nextEpisode.status === 'D'

View File

@ -1,6 +1,5 @@
import {CustomButtonPrimary} from "./CustomButtonPrimary";
import axios, {AxiosResponse} from "axios";
import {apiURL} from "../utils/Utilities";
import {PlaylistDto, PlaylistDtoPost, PlaylistItem} from "../models/Playlist";
import usePlaylist from "../store/PlaylistSlice";
import {useTranslation} from "react-i18next";
@ -18,7 +17,7 @@ export const PlaylistSubmitViewer = ()=>{
episode: item.podcastEpisode.id
}})
axios.post(apiURL+'/playlist', {
axios.post('/playlist', {
name: currentPlaylistToEdit?.name!,
items: idsToMap
} satisfies PlaylistDtoPost)

View File

@ -1,7 +1,7 @@
import { createRef, FC } from 'react'
import { Link } from 'react-router-dom'
import axios from 'axios'
import {apiURL, prependAPIKeyOnAuthEnabled} from '../utils/Utilities'
import {prependAPIKeyOnAuthEnabled} from '../utils/Utilities'
import useCommon, { Podcast } from '../store/CommonSlice'
import 'material-symbols/outlined.css'
@ -14,7 +14,7 @@ export const PodcastCard: FC<PodcastCardProps> = ({ podcast }) => {
const updateLikePodcast = useCommon(state => state.updateLikePodcast)
const likePodcast = () => {
axios.put(apiURL + '/podcast/favored', {
axios.put( '/podcast/favored', {
id: podcast.id,
favored: !podcast.favorites
})

View File

@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'
import { Waypoint } from 'react-waypoint'
import axios, { AxiosResponse } from 'axios'
import { useSnackbar } from 'notistack'
import {apiURL, formatTime, prependAPIKeyOnAuthEnabled, removeHTML} from '../utils/Utilities'
import {formatTime, prependAPIKeyOnAuthEnabled, removeHTML} from '../utils/Utilities'
import 'material-symbols/outlined.css'
import {EpisodesWithOptionalTimeline} from "../models/EpisodesWithOptionalTimeline";
import useCommon from "../store/CommonSlice";
@ -81,7 +81,7 @@ export const PodcastDetailItem: FC<PodcastDetailItemProps> = ({ episode, index,e
return
}
axios.put(apiURL + "/podcast/" + episode.podcastEpisode.episode_id + "/episodes/download")
axios.put( "/podcast/" + episode.podcastEpisode.episode_id + "/episodes/download")
.then(()=>{
enqueueSnackbar(t('episode-downloaded-to-server'), {variant: "success"})
setEpisodeDownloaded(episode.podcastEpisode.episode_id)
@ -113,7 +113,7 @@ export const PodcastDetailItem: FC<PodcastDetailItemProps> = ({ episode, index,e
// Prevent icon click from triggering info modal
e.stopPropagation()
axios.get(apiURL + '/podcast/episode/' + episode.podcastEpisode.episode_id)
axios.get( '/podcast/episode/' + episode.podcastEpisode.episode_id)
.then((response: AxiosResponse<Episode>) => {
handlePlayofEpisode(response, episode)
})
@ -123,7 +123,7 @@ export const PodcastDetailItem: FC<PodcastDetailItemProps> = ({ episode, index,e
{/* Infinite scroll */
index === (episodesLength - 5) &&
<Waypoint key={index + 'waypoint'} onEnter={() => {
axios.get(apiURL + '/podcast/' + params.id + '/episodes',{
axios.get( '/podcast/' + params.id + '/episodes',{
params: {
last_podcast_episode: selectedEpisodes[selectedEpisodes.length - 1].podcastEpisode.date_of_recording
}

View File

@ -1,7 +1,7 @@
import { createPortal } from 'react-dom'
import { useTranslation } from 'react-i18next'
import axios from 'axios'
import { apiURL, preparePath, removeHTML } from '../utils/Utilities'
import { preparePath, removeHTML } from '../utils/Utilities'
import useCommon from '../store/CommonSlice'
import { Heading2 } from './Heading2'
import 'material-symbols/outlined.css'
@ -23,7 +23,7 @@ export const PodcastInfoModal = () => {
}
const deleteEpisodeDownloadOnServer = (episodeId: string) => {
axios.delete(apiURL + '/episodes/' + episodeId + '/download').then(() => {
axios.delete( '/episodes/' + episodeId + '/download').then(() => {
setInfoModalPodcastOpen(false)
})
}

View File

@ -1,7 +1,6 @@
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import axios, { AxiosError, AxiosResponse } from 'axios'
import { apiURL } from '../utils/Utilities'
import { useDebounce } from '../utils/useDebounce'
import { handleAddPodcast } from '../utils/ErrorSnackBarResponses'
import useCommon from '../store/CommonSlice'
@ -31,7 +30,7 @@ export const ProviderImportComponent: FC<ProviderImportComponent> = ({ selectedS
const setModalOpen = useModal(state => state.setOpenModal)
const addPodcast = (podcast: AddPostPostModel) => {
axios.post(apiURL + '/podcast/' + selectedSearchType, podcast)
axios.post( '/podcast/' + selectedSearchType, podcast)
.then((err: any) => {
setModalOpen(false)
err.response.status && handleAddPodcast(err.response.status,
@ -46,7 +45,7 @@ export const ProviderImportComponent: FC<ProviderImportComponent> = ({ selectedS
useDebounce(() => {
setLoading(true)
selectedSearchType === 'itunes' ?
axios.get(apiURL + '/podcasts/0/' + encodeURI(searchText) + '/search')
axios.get( '/podcasts/0/' + encodeURI(searchText) + '/search')
.then((v: AxiosResponse<GeneralModel>) => {
setLoading(false)
const agnosticModel: AgnosticPodcastDataModel[] = v.data.results.map((podcast) => {
@ -60,7 +59,7 @@ export const ProviderImportComponent: FC<ProviderImportComponent> = ({ selectedS
setSearchedPodcasts(agnosticModel)
})
: axios.get(apiURL + '/podcasts/1/' + searchText + '/search')
: axios.get( '/podcasts/1/' + searchText + '/search')
.then((v: AxiosResponse<PodIndexModel>) => {
setLoading(false)
let agnosticModel: AgnosticPodcastDataModel[] = v.data.feeds.map((podcast) => {

View File

@ -2,7 +2,6 @@ import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import axios, { AxiosResponse } from 'axios'
import { useSnackbar } from 'notistack'
import { apiURL } from '../utils/Utilities'
import { Setting } from '../models/Setting'
import { CustomButtonPrimary } from './CustomButtonPrimary'
import { CustomButtonSecondary } from './CustomButtonSecondary'
@ -20,7 +19,7 @@ export const SettingsData: FC = () => {
/* Fetch existing settings */
useEffect(()=>{
axios.get(apiURL + '/settings').then((res: AxiosResponse<Setting>) => {
axios.get( '/settings').then((res: AxiosResponse<Setting>) => {
setSettings(res.data)
})
}, [])
@ -44,7 +43,7 @@ export const Settings: FC<SettingsProps> = ({ initialSettings }) => {
<div>
<label className="mr-6" htmlFor="auto-cleanup">{t('auto-cleanup')}</label>
<CustomButtonSecondary onClick={() => {
axios.put(apiURL+"/settings/runcleanup")
axios.put("/settings/runcleanup")
}}>{t('run-cleanup')}</CustomButtonSecondary>
</div>
<Switcher checked={settings.autoCleanup} className="xs:justify-self-end" id="auto-cleanup" setChecked={() => {
@ -82,7 +81,7 @@ export const Settings: FC<SettingsProps> = ({ initialSettings }) => {
</div>
<CustomButtonPrimary className="float-right" onClick={() => {
axios.put(apiURL + '/settings', settings)
axios.put( '/settings', settings)
.then(() => {
enqueueSnackbar(t('settings-saved'), { variant: 'success' })
})

View File

@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next'
import { Controller, SubmitHandler, useForm } from 'react-hook-form'
import axios, { AxiosResponse } from 'axios'
import { enqueueSnackbar } from 'notistack'
import { apiURL } from '../utils/Utilities'
import { Setting } from '../models/Setting'
import { UpdateNameSettings } from '../models/UpdateNameSettings'
import { CustomButtonPrimary } from './CustomButtonPrimary'
@ -39,7 +38,7 @@ export const SettingsNaming: FC = () => {
/* Fetch existing settings */
useEffect(() => {
axios.get(apiURL + '/settings')
axios.get('/settings')
.then((res:AxiosResponse<Setting>) => {
setSettings(res.data)
})
@ -78,7 +77,7 @@ const Settings: FC<SettingsProps> = ({ intialSettings }) => {
const content = {
content: episodeFormat
}
axios.post(apiURL + '/episodes/formatting', content)
axios.post( '/episodes/formatting', content)
.then((v: AxiosResponse<string>)=>setResultingEpisodeFormat(v.data))
.catch(e=>setResultingEpisodeFormat(e.response.data.error))
@ -88,13 +87,13 @@ const Settings: FC<SettingsProps> = ({ intialSettings }) => {
const content = {
content: podcastFormat
}
axios.post(apiURL + '/podcasts/formatting', content)
axios.post( '/podcasts/formatting', content)
.then((v: AxiosResponse<string>)=>setResultingPodcastFormat(v.data))
.catch(e=>setResultingPodcastFormat(e.response.data.error))
},2000,[podcastFormat])
const update_settings: SubmitHandler<UpdateNameSettings> = (data) => {
axios.put(apiURL + '/settings/name', data satisfies UpdateNameSettings)
axios.put( '/settings/name', data satisfies UpdateNameSettings)
.then(() => {
enqueueSnackbar(t('settings-saved'), { variant: 'success' })
})

View File

@ -1,7 +1,6 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import axios from 'axios'
import { apiURL } from '../utils/Utilities'
import { CustomButtonSecondary } from './CustomButtonSecondary'
import 'material-symbols/outlined.css'
@ -10,7 +9,7 @@ export const SettingsOPMLExport: FC = () => {
const downloadOPML = (exportType: string) => {
axios({
url: apiURL + '/settings/opml/' + exportType,
url: '/settings/opml/' + exportType,
method: 'GET',
responseType: 'blob'
}).then((response) => {

View File

@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next'
import axios from 'axios'
import { enqueueSnackbar } from 'notistack'
import useCommon, { Podcast} from '../store/CommonSlice'
import { apiURL } from '../utils/Utilities'
import { CustomButtonSecondary } from './CustomButtonSecondary'
import useModal from "../store/ModalSlice";
@ -18,7 +17,7 @@ export const SettingsPodcastDelete: FC = () => {
useEffect(() => {
if (podcasts.length === 0) {
axios.get(apiURL + '/podcasts')
axios.get('/podcasts')
.then((v) => {
setPodcasts(v.data)
})
@ -26,7 +25,7 @@ export const SettingsPodcastDelete: FC = () => {
}, [])
const deletePodcast = (withFiles: boolean, podcast_id: number, p: Podcast) => {
axios.delete(apiURL + '/podcast/' + podcast_id, { data: { delete_files: withFiles }})
axios.delete( '/podcast/' + podcast_id, { data: { delete_files: withFiles }})
.then(() => {
enqueueSnackbar(t('podcast-deleted', { name: p.name }), { variant: 'success' })
podcastDeleted(podcast_id)

View File

@ -2,10 +2,8 @@ import { FC } from 'react'
import { Waypoint } from 'react-waypoint'
import axios, { AxiosResponse } from 'axios'
import useCommon from '../store/CommonSlice'
import { apiURL } from '../utils/Utilities'
import { TimelineHATEOASModel, TimeLineModel } from '../models/TimeLineModel'
import { EpisodeCard } from './EpisodeCard'
import {PodcastWatchedModel} from "../models/PodcastWatchedModel";
import {Episode} from "../models/Episode";
type TimelineEpisodeProps = {
@ -28,7 +26,7 @@ export const TimelineEpisode: FC<TimelineEpisodeProps> = ({ podcastEpisode,podca
{/*Infinite scroll */
timeLineEpisodes.data.length === index + 1 &&
<Waypoint key={index + 'waypoint'} onEnter={() => {
axios.get(apiURL + '/podcasts/timeline', {
axios.get('/podcasts/timeline', {
params:{
lastTimestamp: podcastEpisode.podcast_episode.date_of_recording,
favoredOnly: useCommon.getState().filters?.onlyFavored,

View File

@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
import axios from 'axios'
import { useSnackbar } from 'notistack'
import useCommon from '../store/CommonSlice'
import { apiURL, formatTime } from '../utils/Utilities'
import {formatTime } from '../utils/Utilities'
import { CreateInviteModal } from './CreateInviteModal'
import { CustomButtonPrimary } from './CustomButtonPrimary'
import { CustomSelect, Option } from './CustomSelect'
@ -75,7 +75,7 @@ export const UserAdminInvites = () => {
}, [invites, selectedInviteType])
useEffect(() => {
axios.get(apiURL + '/users/invites')
axios.get( '/users/invites')
.then(v => {
setInvites(v.data)
setError(false)
@ -139,7 +139,7 @@ export const UserAdminInvites = () => {
<tr className="border-b border-[--border-color]" key={i.id}>
<td className="pr-2 py-4">
<button className="text-left text-[--fg-color] hover:text-[--fg-color-hover]" onClick={() => {
axios.get(apiURL + '/users/invites/' + i.id + '/link')
axios.get( '/users/invites/' + i.id + '/link')
.then(v => {
navigator.clipboard.writeText(v.data)
.then(()=>enqueueSnackbar(t('invite-link-copied'), { autoHideDuration: 2000 }))
@ -168,7 +168,7 @@ export const UserAdminInvites = () => {
</td>
<td className="pl-2 py-4">
<button className="flex items-center float-right text-[--danger-fg-color] hover:text-[--danger-fg-color-hover]" onClick={() => {
axios.delete(apiURL + '/users/invites/' + i.id)
axios.delete( '/users/invites/' + i.id)
.then(() => {
enqueueSnackbar(t('invite-deleted'), { variant: 'success' })
setInvites(invites.filter(v => v.id !== i.id))

View File

@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
import axios from 'axios'
import { useSnackbar } from 'notistack'
import useCommon from '../store/CommonSlice'
import { apiURL, formatTime} from '../utils/Utilities'
import { formatTime} from '../utils/Utilities'
import { User } from '../models/User'
import { ChangeRoleModal } from './ChangeRoleModal'
import { Loading } from './Loading'
@ -21,7 +21,7 @@ export const UserAdminUsers = () => {
const setModalOpen = useModal(state => state.setOpenModal)
useEffect(() => {
axios.get(apiURL + '/users')
axios.get( '/users')
.then(c => {
setUsers(c.data)
setError(false)
@ -30,7 +30,7 @@ export const UserAdminUsers = () => {
}, [])
const deleteUser = (user: User) => {
axios.delete(apiURL + '/users/' + user.username)
axios.delete( '/users/' + user.username)
.then(() => {
enqueueSnackbar(t('user-deleted'), { variant: 'success' })
setUsers(users.filter(u => u.username !== user.username))

View File

@ -1,3 +1,4 @@
import "./utils/navigationUtils"
import React, { FC, PropsWithChildren, useEffect } from 'react'
import ReactDOM from 'react-dom/client'
import { I18nextProvider } from 'react-i18next'
@ -8,7 +9,6 @@ import { SnackbarProvider } from 'notistack'
import { router } from './App'
import useCommon from './store/CommonSlice'
import i18n from './language/i18n'
import { apiURL } from './utils/Utilities'
import { ConfigModel } from './models/SysInfo'
import { Loading } from './components/Loading'
import { OIDCRefresher } from './components/OIDCRefresher'
@ -24,13 +24,24 @@ import '@fontsource/poppins/700.css'
import '@fontsource/poppins/700-italic.css'
import './index.css'
import './assets/scss/style.scss'
export let apiURL: string
export let uiURL: string
if (window.location.pathname.startsWith("/ui")) {
apiURL = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/api/v1"
} else {
//match everything before /ui
const regex = /\/([^/]+)\/ui\//
apiURL = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/" + regex.exec(window.location.href)![1] + "/api/v1"
}
uiURL = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/ui"
const AuthWrapper: FC<PropsWithChildren> = ({ children }) => {
const configModel = useCommon(state => state.configModel)
const setConfigModel = useCommon(state => state.setConfigModel)
useEffect(() => {
axios.get(apiURL + '/sys/config').then((v: AxiosResponse<ConfigModel>) => {
axios.defaults.baseURL = apiURL
axios.get('/sys/config').then((v: AxiosResponse<ConfigModel>) => {
setConfigModel(v.data)
})
}, [])

View File

@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'
import { useNavigate, useParams } from 'react-router-dom'
import axios from 'axios'
import { enqueueSnackbar } from 'notistack'
import { apiURL, formatTime } from '../utils/Utilities'
import { formatTime } from '../utils/Utilities'
import { CustomButtonPrimary } from '../components/CustomButtonPrimary'
import { CustomInput } from '../components/CustomInput'
import { Heading2 } from '../components/Heading2'
@ -29,7 +29,7 @@ export const AcceptInvite = () => {
}
useEffect(() => {
axios.get(apiURL + '/users/invites/' + params.id)
axios.get( '/users/invites/' + params.id)
.then((res) => {
setInvite(res.data)
})
@ -43,7 +43,7 @@ export const AcceptInvite = () => {
}
const onSubmit: SubmitHandler<LoginData> = (data) => {
axios.post(apiURL + '/users/', {
axios.post( '/users/', {
username: data.username,
password: data.password,
inviteId: params.id

View File

@ -2,7 +2,6 @@ import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import axios from 'axios'
import { apiURL} from '../utils/Utilities'
import { PodcastWatchedEpisodeModel } from '../models/PodcastWatchedEpisodeModel'
import { TimeLineModel, TimelineHATEOASModel } from '../models/TimeLineModel'
import { EpisodeCard } from '../components/EpisodeCard'
@ -15,12 +14,12 @@ export const Homepage = () => {
const { t } = useTranslation()
useEffect(()=>{
axios.get<PodcastWatchedEpisodeModel[]>(apiURL + '/podcast/episode/lastwatched')
axios.get<PodcastWatchedEpisodeModel[]>('/podcast/episode/lastwatched')
.then((response) => {
setPodcastWatched(response.data)
})
axios.get<TimelineHATEOASModel>(apiURL + '/podcasts/timeline', {
axios.get<TimelineHATEOASModel>('/podcasts/timeline', {
params: {
favoredOnly: false,
notListened: false

View File

@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import axios, { AxiosError } from 'axios'
import useCommon from '../store/CommonSlice'
import { apiURL } from '../utils/Utilities'
import { CustomButtonPrimary } from '../components/CustomButtonPrimary'
import { CustomCheckbox } from '../components/CustomCheckbox'
import { CustomInput } from '../components/CustomInput'
@ -36,7 +35,7 @@ export const Login = () => {
const onSubmit: SubmitHandler<LoginData> = (data, p) => {
p?.preventDefault()
axios.post(apiURL + '/login', data)
axios.post('/login', data)
.then(() => {
const basicAuthString = btoa(data.username + ':' + data.password)

View File

@ -3,12 +3,11 @@ import {PodcastDetailItem} from "../components/PodcastDetailItem";
import {useTranslation} from "react-i18next";
import {useParams} from "react-router-dom";
import axios, {AxiosResponse} from "axios";
import {apiURL, prepareOnlinePodcastEpisode, preparePodcastEpisode} from "../utils/Utilities";
import {prepareOnlinePodcastEpisode, preparePodcastEpisode} from "../utils/Utilities";
import {PlaylistDto} from "../models/Playlist";
import usePlaylist from "../store/PlaylistSlice";
import {useEffect} from "react";
import useAudioPlayer from "../store/AudioPlayerSlice";
import {PodcastWatchedModel} from "../models/PodcastWatchedModel";
import {PodcastInfoModal} from "../components/PodcastInfoModal";
import {PodcastEpisodeAlreadyPlayed} from "../components/PodcastEpisodeAlreadyPlayed";
import {Episode} from "../models/Episode";
@ -26,14 +25,14 @@ export const PlaylistDetailPage = ()=>{
useEffect(() => {
if(metadata){
if(metadata.percentage>99){
axios.delete(apiURL+"/playlist/"+params.id+"/episode/"+current_podcast_episode!.id)
axios.delete("/playlist/"+params.id+"/episode/"+current_podcast_episode!.id)
.then(()=>{
const currentIndex = selectedPlaylist!.items.findIndex(i=>i.podcastEpisode.id===current_podcast_episode!.id)
if(currentIndex === selectedPlaylist!.items.length-1){
return
}
const nextEpisode = selectedPlaylist!.items[currentIndex+1]
axios.get(apiURL + "/podcast/episode/" + nextEpisode.podcastEpisode.episode_id)
axios.get("/podcast/episode/" + nextEpisode.podcastEpisode.episode_id)
.then((response: AxiosResponse<Episode>) => {
nextEpisode.podcastEpisode.status === 'D'
? setCurrentPodcastEpisode(preparePodcastEpisode(nextEpisode.podcastEpisode, response.data))
@ -54,7 +53,7 @@ export const PlaylistDetailPage = ()=>{
useEffect(()=>{
axios.get(apiURL+"/playlist/"+params.id)
axios.get("/playlist/"+params.id)
.then((response:AxiosResponse<PlaylistDto>)=>{
setSelectedPlaylist(response.data)
})

View File

@ -1,6 +1,5 @@
import {CustomButtonPrimary} from "../components/CustomButtonPrimary";
import axios, {AxiosResponse} from "axios";
import {apiURL} from "../utils/Utilities";
import {useTranslation} from "react-i18next";
import {enqueueSnackbar} from "notistack";
import usePlaylist from "../store/PlaylistSlice";
@ -19,7 +18,7 @@ export const PlaylistPage = ()=>{
useEffect(()=>{
if (playlist.length ===0){
axios.get(apiURL+"/playlist").then((response:AxiosResponse<PlaylistDto[]>)=>{
axios.get("/playlist").then((response:AxiosResponse<PlaylistDto[]>)=>{
setPlaylist(response.data)
})
}
@ -58,7 +57,7 @@ export const PlaylistPage = ()=>{
<button className="flex ml-2" onClick={(e)=>{
e.preventDefault()
axios.get(apiURL+"/playlist/"+i.id).then((response:AxiosResponse<PlaylistDto>)=> {
axios.get("/playlist/"+i.id).then((response:AxiosResponse<PlaylistDto>)=> {
setCurrentPlaylistToEdit(response.data)
setCreatePlaylistOpen(true)
})
@ -80,7 +79,7 @@ export const PlaylistPage = ()=>{
</button>
<button className="flex float-right text-red-700 hover:text-red-500" onClick={(e)=>{
e.preventDefault()
axios.delete(apiURL+"/playlist/"+i.id).then(()=>{
axios.delete("/playlist/"+i.id).then(()=>{
enqueueSnackbar(t('invite-deleted'), {variant: "success"})
setPlaylist(playlist.filter(v=>v.id !== i.id))
})

View File

@ -2,7 +2,7 @@ import {Fragment, useEffect, useState} from 'react'
import {useParams} from 'react-router-dom'
import {useTranslation} from 'react-i18next'
import axios, {AxiosResponse} from 'axios'
import {apiURL, prependAPIKeyOnAuthEnabled, removeHTML} from '../utils/Utilities'
import {prependAPIKeyOnAuthEnabled, removeHTML} from '../utils/Utilities'
import useCommon, {Podcast} from '../store/CommonSlice'
import useAudioPlayer from '../store/AudioPlayerSlice'
import {Chip} from '../components/Chip'
@ -33,10 +33,10 @@ export const PodcastDetailPage = () => {
setCurrentDetailedPodcastId(Number(params.id))
}
axios.get(apiURL + '/podcast/' + params.id).then((response: AxiosResponse<Podcast>) => {
axios.get('/podcast/' + params.id).then((response: AxiosResponse<Podcast>) => {
setCurrentPodcast(response.data)
}).then(() => {
axios.get(apiURL + '/podcast/' + params.id + '/episodes')
axios.get('/podcast/' + params.id + '/episodes')
.then((response: AxiosResponse<EpisodesWithOptionalTimeline[]>) => {
setSelectedEpisodes(response.data)
@ -127,7 +127,7 @@ export const PodcastDetailPage = () => {
<span
className="material-symbols-outlined inline cursor-pointer align-middle text-[--fg-icon-color] hover:text-[--fg-icon-color-hover]"
onClick={() => {
axios.post(apiURL + '/podcast/' + params.id + '/refresh')
axios.post('/podcast/' + params.id + '/refresh')
.then(() => {
console.log('Refreshed')
})
@ -176,7 +176,7 @@ export const PodcastDetailPage = () => {
<span className="text-xs text-[--fg-secondary-color]">{t('active')}</span>
<Switcher checked={currentPodcast.active} setChecked={() => {
axios.put(apiURL + '/podcast/' + params.id + '/active')
axios.put('/podcast/' + params.id + '/active')
.then(() => {
setCurrentPodcast({...currentPodcast, active: !currentPodcast?.active})
})

View File

@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'
import axios, { AxiosResponse } from 'axios'
import { useDebounce } from '../utils/useDebounce'
import {
apiURL,
getFiltersDefault,
OrderCriteriaSortingType, TIME_ASCENDING, TIME_DESCENDING,
TITLE_ASCENDING,
@ -47,7 +46,7 @@ export const Podcasts: FC<PodcastsProps> = ({ onlyFavorites }) => {
}, [filters])
const refreshAllPodcasts = () => {
axios.post(apiURL + '/podcast/all')
axios.post( '/podcast/all')
}
const performFilter = () => {
@ -55,7 +54,7 @@ export const Podcasts: FC<PodcastsProps> = ({ onlyFavorites }) => {
return
}
axios.get(apiURL + '/podcasts/search', {
axios.get('/podcasts/search', {
params: {
title: filters?.title,
order: filters?.ascending?Order.ASC:Order.DESC,
@ -73,7 +72,7 @@ export const Podcasts: FC<PodcastsProps> = ({ onlyFavorites }) => {
},500, [filters])
useEffect(() => {
axios.get(apiURL + '/podcasts/filter')
axios.get('/podcasts/filter')
.then((c: AxiosResponse<Filter>) => {
if (c.data === null) {
setFilters(getFiltersDefault())

View File

@ -1,7 +1,7 @@
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import axios, { AxiosResponse } from 'axios'
import {apiURL, prependAPIKeyOnAuthEnabled} from '../utils/Utilities'
import {prependAPIKeyOnAuthEnabled} from '../utils/Utilities'
import { DiskModel } from '../models/DiskModel'
import { SysExtraInfo } from '../models/SysExtraInfo'
import { CustomGaugeChart } from '../components/CustomGaugeChart'
@ -30,13 +30,13 @@ export const SystemInfoPage: FC = () => {
const megaByte = Math.pow(10,6)
useEffect(() => {
axios.get(apiURL + '/sys/info')
axios.get('/sys/info')
.then((response: AxiosResponse<SysExtraInfo>) => setSystemInfo(response.data))
axios.get(apiURL + '/info')
axios.get('/info')
.then(c => setVersionInfo(c.data))
const updateInterval = setInterval(() => {
axios.get(apiURL + '/sys/info')
axios.get('/sys/info')
.then((response: AxiosResponse<SysExtraInfo>) => setSystemInfo(response.data))
}, 5000)
return () => clearInterval(updateInterval)

View File

@ -1,7 +1,7 @@
import {Fragment, useEffect, useState} from 'react'
import { useTranslation } from 'react-i18next'
import axios, { AxiosResponse } from 'axios'
import { apiURL, formatTime, getFiltersDefault } from '../utils/Utilities'
import { formatTime, getFiltersDefault } from '../utils/Utilities'
import useCommon from '../store/CommonSlice'
import { Filter } from '../models/Filter'
import { TimelineHATEOASModel } from '../models/TimeLineModel'
@ -20,7 +20,7 @@ export const Timeline = () => {
useEffect(() => {
!filter && axios.get(apiURL + '/podcasts/filter')
!filter && axios.get( '/podcasts/filter')
.then((filterAxiosResponse: AxiosResponse<Filter>) => {
filterAxiosResponse.data == null && setFilters(getFiltersDefault())
@ -32,7 +32,7 @@ export const Timeline = () => {
if (filter) {
let favoredOnly = filter?.onlyFavored
axios.get(apiURL + '/podcasts/timeline', {
axios.get('/podcasts/timeline', {
params: {
favoredOnly: favoredOnly === undefined ? false : favoredOnly,
notListened: notListened

View File

@ -7,7 +7,6 @@ import {Controller, useForm} from "react-hook-form";
import {CustomCheckbox} from "../components/CustomCheckbox";
import {CustomButtonPrimary} from "../components/CustomButtonPrimary";
import axios, {AxiosResponse} from "axios";
import {apiURL} from "../utils/Utilities";
import {v4} from "uuid";
import {enqueueSnackbar} from "notistack";
@ -41,7 +40,7 @@ export const UserManagementPage: FC<UserManagementPageProps> = () => {
if (data.password === '') {
delete data.password
}
axios.put(apiURL+'/users/'+loggedInUser?.username, data)
axios.put('/users/'+loggedInUser?.username, data)
.then((c:AxiosResponse<LoggedInUser>)=>{
useCommon.getState().setLoggedInUser(c.data)
enqueueSnackbar(t('user-settings-updated'), {variant: 'success'})

View File

@ -2,7 +2,6 @@ import { useEffect } from 'react'
import { Outlet, useNavigate } from 'react-router-dom'
import axios from 'axios'
import useCommon from '../store/CommonSlice'
import { configWSUrl } from '../utils/Utilities'
import App from '../App'
import { AudioComponents } from '../components/AudioComponents'
import { EpisodeSearchModal } from '../components/EpisodeSearchModal'
@ -10,6 +9,7 @@ import { Header } from '../components/Header'
import { Loading } from '../components/Loading'
import { MainContentPanel } from '../components/MainContentPanel'
import { Sidebar } from '../components/Sidebar'
import {configWSUrl} from "../utils/navigationUtils";
export const Root = () => {
const configModel = useCommon(state => state.configModel)

View File

@ -34,31 +34,6 @@ TimeAgo.addLocale(fr)
export const SKIPPED_TIME = 30
let timeago = new TimeAgo('en-US')
export let apiURL: string
export let uiURL: string
if (window.location.pathname.startsWith("/ui")) {
apiURL = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/api/v1"
} else {
//match everything before /ui
const regex = /\/([^/]+)\/ui\//
apiURL = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/" + regex.exec(window.location.href)![1] + "/api/v1"
}
uiURL = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/ui"
const wsEndpoint = "ws"
export const configWSUrl = (url: string) => {
if (url.startsWith("http")) {
return url.replace("http", "ws") + wsEndpoint
}
return url.replace("https", "wss") + wsEndpoint
}
export const logCurrentPlaybackTime = (episodeId: string, timeInSeconds: number) => {
axios.post(apiURL + "/podcast/episode", {
podcastEpisodeId: episodeId,
time: Number(timeInSeconds.toFixed(0))
})
}
export const formatTime = (isoDate: string) => {
if(Number.isNaN(Date.parse(isoDate))) return ""
@ -182,4 +157,8 @@ export const TITLE_DESCENDING:OrderCriteriaSortingType = {
ascending: false
}
export const decodeHTMLEntities = (() => { const textArea = document.createElement('textarea'); return (message: string): string => { textArea.innerHTML = message; return textArea.value; }; })();
export const decodeHTMLEntities = (() => { const textArea = document.createElement('textarea');
return (message: string): string => {
textArea.innerHTML = message;
return textArea.value;
}; })();

View File

@ -0,0 +1,18 @@
import axios from "axios";
const wsEndpoint = "ws"
export const configWSUrl = (url: string) => {
if (url.startsWith("http")) {
return url.replace("http", "ws") + wsEndpoint
}
return url.replace("https", "wss") + wsEndpoint
}
export const logCurrentPlaybackTime = (episodeId: string, timeInSeconds: number) => {
axios.post("/podcast/episode", {
podcastEpisodeId: episodeId,
time: Number(timeInSeconds.toFixed(0))
})
}

View File

@ -0,0 +1,10 @@
import {describe, it, expect} from "vitest";
import {decodeHTMLEntities} from "../src/utils/Utilities";
describe("Html code replacement", () => {
it("should replace the html code", () => {
const html = "<div>Pourquoi les ministres n&#39;ont plus le droit d&#39;utilisier</div>";
const replacedHtml = decodeHTMLEntities(html);
expect(replacedHtml).toBe("<div>Pourquoi les ministres n'ont plus le droit d'utilisier</div>");
});
})