apply suggestions
continuous-integration/drone/push Build is passing Details

maxime 1 year ago
parent 1d1ac1e088
commit bf5de96871

@ -1,2 +1,2 @@
VITE_API_ENDPOINT=https://iqball.maxou.dev/api/dotnet-master #VITE_API_ENDPOINT=https://iqball.maxou.dev/api/dotnet-master
#VITE_API_ENDPOINT=http://localhost:5254 VITE_API_ENDPOINT=http://localhost:5254

@ -17,4 +17,9 @@ module.exports = {
{ allowConstantExport: true }, { allowConstantExport: true },
], ],
}, },
settings: {
react: {
version: "detect",
},
},
} }

@ -3,42 +3,41 @@ type: docker
name: "CI and Deploy on maxou.dev" name: "CI and Deploy on maxou.dev"
volumes: volumes:
- name: server - name: server
temp: { } temp: {}
trigger: trigger:
event: event:
- push - push
steps: steps:
- image: node:latest - image: node:latest
name: "front CI" name: "front CI"
commands: commands:
- npm install - npm install
- npm run tsc - npm run tsc
- image: node:latest - image: node:latest
name: "build react" name: "build react"
volumes: &outputs volumes: &outputs
- name: server - name: server
path: /outputs path: /outputs
depends_on: depends_on:
- "front CI" - "front CI"
commands: commands:
- # force to use the backend master branch if pushing on master - # force to use the backend master branch if pushing on masterŒ
- echo "VITE_API_ENDPOINT=https://iqball.maxou.dev/api/dotnet-$([ "$DRONE_BRANCH" = master ] && echo master || cat .stage-backend-branch | tr / _)" > .env.STAGE - echo "VITE_API_ENDPOINT=https://iqball.maxou.dev/api/dotnet-$([ "$DRONE_BRANCH" = master ] && echo master || cat .stage-backend-branch | tr / _)" > .env.STAGE
- npm run build -- --base=/$DRONE_BRANCH/ --mode STAGE - npm run build -- --base=/$DRONE_BRANCH/ --mode STAGE
- mv dist/* /outputs - mv dist/* /outputs
- image: eeacms/rsync:latest
- image: eeacms/rsync:latest name: Deliver on staging server branch
name: Deliver on staging server branch depends_on:
depends_on: - "build react"
- "build react" volumes: *outputs
volumes: *outputs environment:
environment: SERVER_PRIVATE_KEY:
SERVER_PRIVATE_KEY: from_secret: SERVER_PRIVATE_KEY
from_secret: SERVER_PRIVATE_KEY commands:
commands: - chmod +x ci/deploy.sh
- chmod +x ci/deploy.sh - ci/deploy.sh
- ci/deploy.sh

@ -6,4 +6,7 @@ export const API = import.meta.env.VITE_API_ENDPOINT
/** /**
* This constant defines the base app's endpoint. * This constant defines the base app's endpoint.
*/ */
export const BASE = import.meta.env.BASE_URL.slice(0, import.meta.env.BASE_URL.length - 1) export const BASE = import.meta.env.BASE_URL.slice(
0,
import.meta.env.BASE_URL.length - 1,
)

@ -1,12 +1,10 @@
import { API } from "./Constants" import { API } from "./Constants"
import { getSession, saveSession, Session } from "./api/session.ts" import { getSession, saveSession, Session } from "./api/session.ts"
import { redirect } from "react-router-dom"
export async function fetchAPI( export async function fetchAPI(
url: string, url: string,
payload: unknown, payload: unknown,
method = "POST", method = "POST",
redirectIfUnauthorized: boolean = true,
): Promise<Response> { ): Promise<Response> {
const session = getSession() const session = getSession()
const token = session?.auth?.token const token = session?.auth?.token
@ -26,13 +24,10 @@ export async function fetchAPI(
body: JSON.stringify(payload), body: JSON.stringify(payload),
}) })
return await handleResponse(session, response, redirectIfUnauthorized) return await handleResponse(session, response)
} }
export async function fetchAPIGet( export async function fetchAPIGet(url: string): Promise<Response> {
url: string,
redirectIfNotAuth: boolean = true,
): Promise<Response> {
const session = getSession() const session = getSession()
const token = session?.auth?.token const token = session?.auth?.token
@ -50,20 +45,16 @@ export async function fetchAPIGet(
headers, headers,
}) })
return await handleResponse(session, response, redirectIfNotAuth) return await handleResponse(session, response)
} }
async function handleResponse( async function handleResponse(
session: Session, session: Session,
response: Response, response: Response,
redirectIfNotAuth: boolean,
): Promise<Response> { ): Promise<Response> {
// if we provided a token but still unauthorized, the token has expired // if we provided a token but still unauthorized, the token has expired
console.log(response.status) if (!response.ok) {
if (response.status == 401) { return response
if (!redirectIfNotAuth) return response
saveSession({ ...session, urlTarget: location.pathname })
return redirect("/login")
} }
const nextToken = response.headers.get("Next-Authorization")! const nextToken = response.headers.get("Next-Authorization")!

@ -72,7 +72,7 @@ import {
removePlayer, removePlayer,
} from "../editor/PlayerDomains" } from "../editor/PlayerDomains"
import { CourtBall } from "../components/editor/CourtBall" import { CourtBall } from "../components/editor/CourtBall"
import { useParams } from "react-router-dom" import { useNavigate, useParams } from "react-router-dom"
import { DEFAULT_TACTIC_NAME } from "./NewTacticPage.tsx" import { DEFAULT_TACTIC_NAME } from "./NewTacticPage.tsx"
const ERROR_STYLE: CSSProperties = { const ERROR_STYLE: CSSProperties = {
@ -108,26 +108,35 @@ export default function EditorPage({ guestMode }: EditorPageProps) {
name: DEFAULT_TACTIC_NAME, name: DEFAULT_TACTIC_NAME,
} }
} }
return null; return null
}) })
const { tacticId: idStr } = useParams() const { tacticId: idStr } = useParams()
const id = guestMode ? -1 : parseInt(idStr!) const id = guestMode ? -1 : parseInt(idStr!)
const navigation = useNavigate()
useEffect(() => { useEffect(() => {
if (guestMode) if (guestMode) return
return
async function initialize() { async function initialize() {
const infoResponse = fetchAPIGet(`tactics/${id}`) const infoResponsePromise = fetchAPIGet(`tactics/${id}`)
const contentResponse = fetchAPIGet(`tactics/${id}/1`) const contentResponsePromise = fetchAPIGet(`tactics/${id}/1`)
const { name, courtType } = await (await infoResponse).json()
const { content } = await (await contentResponse).json() const infoResponse = await infoResponsePromise
const contentResponse = await contentResponsePromise
if (infoResponse.status == 401 || contentResponse.status == 401) {
navigation("/login")
return
}
const { name, courtType } = await infoResponse.json()
const { content } = await contentResponse.json()
setTactic({ id, name, courtType, content }) setTactic({ id, name, courtType, content })
} }
initialize() initialize()
}, [guestMode, id, idStr]) }, [guestMode, id, idStr, navigation])
if (tactic) { if (tactic) {
return ( return (
@ -164,6 +173,8 @@ function Editor({ id, name, courtType, content }: EditorProps) {
const storageName = localStorage.getItem(GUEST_MODE_TITLE_STORAGE_KEY) const storageName = localStorage.getItem(GUEST_MODE_TITLE_STORAGE_KEY)
const editorName = isInGuestMode && storageName != null ? storageName : name const editorName = isInGuestMode && storageName != null ? storageName : name
const navigate = useNavigate()
return ( return (
<EditorView <EditorView
tactic={{ tactic={{
@ -180,18 +191,31 @@ function Editor({ id, name, courtType, content }: EditorProps) {
) )
return SaveStates.Guest return SaveStates.Guest
} }
return fetchAPI(`tactics/${id}/1`, { content }, "PUT").then( const response = await fetchAPI(
(r) => (r.ok ? SaveStates.Ok : SaveStates.Err), `tactics/${id}/1`,
{ content },
"PUT",
) )
if (response.status == 401) {
navigate("/login")
}
return response.ok ? SaveStates.Ok : SaveStates.Err
}} }}
onNameChange={async (name: string) => { onNameChange={async (name: string) => {
if (isInGuestMode) { if (isInGuestMode) {
localStorage.setItem(GUEST_MODE_TITLE_STORAGE_KEY, name) localStorage.setItem(GUEST_MODE_TITLE_STORAGE_KEY, name)
return true //simulate that the name has been changed return true //simulate that the name has been changed
} }
return fetchAPI(`tactics/${id}/name`, { name }, "PUT").then(
(r) => r.ok, const response = await fetchAPI(
`tactics/${id}/name`,
{ name },
"PUT",
) )
if (response.status == 401) {
navigate("/login")
}
return response.ok
}} }}
/> />
) )

@ -1,6 +1,4 @@
import "../style/home/home.css" import "../style/home/home.css"
import { BASE } from "../Constants"
import { getSession } from "../api/session.ts" import { getSession } from "../api/session.ts"
import { useNavigate } from "react-router-dom" import { useNavigate } from "react-router-dom"
import { startTransition, useLayoutEffect, useState } from "react" import { startTransition, useLayoutEffect, useState } from "react"
@ -42,6 +40,10 @@ export default function HomePage() {
async function getUser() { async function getUser() {
const response = await fetchAPIGet("user-data") const response = await fetchAPIGet("user-data")
if (response.status == 401) {
navigate("/login")
return // if unauthorized
}
setInfo(await response.json()) setInfo(await response.json())
} }
@ -166,6 +168,8 @@ function TableData({ allTactics }: { allTactics: Tactic[] }) {
} }
}) })
const navigate = useNavigate()
i = 0 i = 0
while (i < nbRow) { while (i < nbRow) {
listTactic[i] = listTactic[i].map((tactic: Tactic) => ( listTactic[i] = listTactic[i].map((tactic: Tactic) => (
@ -173,7 +177,7 @@ function TableData({ allTactics }: { allTactics: Tactic[] }) {
key={tactic.id} key={tactic.id}
className="data" className="data"
onClick={() => { onClick={() => {
location.pathname = BASE + "/tactic/" + tactic.id + "/edit" navigate("/tactic/" + tactic.id + "/edit")
}}> }}>
{truncateString(tactic.name, 25)} {truncateString(tactic.name, 25)}
</td> </td>
@ -212,13 +216,12 @@ function BodyPersonalSpace({ allTactics }: { allTactics: Tactic[] }) {
} }
function Team({ teams }: { teams: Team[] }) { function Team({ teams }: { teams: Team[] }) {
const navigate = useNavigate()
return ( return (
<div id="teams"> <div id="teams">
<div className="titre-side-menu"> <div className="titre-side-menu">
<h2 className="title">Mes équipes</h2> <h2 className="title">Mes équipes</h2>
<button <button className="new" onClick={() => navigate("/team/new")}>
className="new"
onClick={() => (location.pathname = BASE + "/team/new")}>
+ +
</button> </button>
</div> </div>
@ -228,6 +231,8 @@ function Team({ teams }: { teams: Team[] }) {
} }
function Tactic({ lastTactics }: { lastTactics: Tactic[] }) { function Tactic({ lastTactics }: { lastTactics: Tactic[] }) {
const navigate = useNavigate()
return ( return (
<div id="tactic"> <div id="tactic">
<div className="titre-side-menu"> <div className="titre-side-menu">
@ -235,7 +240,7 @@ function Tactic({ lastTactics }: { lastTactics: Tactic[] }) {
<button <button
className="new" className="new"
id="create-tactic" id="create-tactic"
onClick={() => (location.pathname = BASE + "/tactic/new")}> onClick={() => navigate("/tactic/new")}>
+ +
</button> </button>
</div> </div>
@ -246,25 +251,29 @@ function Tactic({ lastTactics }: { lastTactics: Tactic[] }) {
function SetButtonTactic({ tactics }: { tactics: Tactic[] }) { function SetButtonTactic({ tactics }: { tactics: Tactic[] }) {
const lastTactics = tactics.map((tactic) => ( const lastTactics = tactics.map((tactic) => (
<ButtonLastTactic tactic={tactic} /> <ButtonLastTactic key={tactic.id} tactic={tactic} />
)) ))
return <div className="set-button">{lastTactics}</div> return <div className="set-button">{lastTactics}</div>
} }
function SetButtonTeam({ teams }: { teams: Team[] }) { function SetButtonTeam({ teams }: { teams: Team[] }) {
const listTeam = teams.map((teams) => <ButtonTeam team={teams} />) const listTeam = teams.map((team) => (
<ButtonTeam key={team.id} team={team} />
))
return <div className="set-button">{listTeam}</div> return <div className="set-button">{listTeam}</div>
} }
function ButtonTeam({ team }: { team: Team }) { function ButtonTeam({ team }: { team: Team }) {
const name = truncateString(team.name, 20) const name = truncateString(team.name, 20)
const navigate = useNavigate()
return ( return (
<div> <div>
<div <div
id={"button-team" + team.id} id={"button-team" + team.id}
className="button-side-menu data" className="button-side-menu data"
onClick={() => { onClick={() => {
location.pathname = BASE + "/team/" + team.id navigate("/team/" + team.id)
}}> }}>
{name} {name}
</div> </div>
@ -274,12 +283,14 @@ function ButtonTeam({ team }: { team: Team }) {
function ButtonLastTactic({ tactic }: { tactic: Tactic }) { function ButtonLastTactic({ tactic }: { tactic: Tactic }) {
const name = truncateString(tactic.name, 20) const name = truncateString(tactic.name, 20)
const navigate = useNavigate()
return ( return (
<div <div
id={"button" + tactic.id} id={"button" + tactic.id}
className="button-side-menu data" className="button-side-menu data"
onClick={() => { onClick={() => {
location.pathname = BASE + "/tactic/" + tactic.id + "/edit" navigate("/tactic/" + tactic.id + "/edit")
}}> }}>
{name} {name}
</div> </div>

@ -1,10 +1,9 @@
import { FormEvent, startTransition, useState } from "react" import { FormEvent, startTransition, useState } from "react"
import { BASE } from "../Constants.ts"
import { fetchAPI } from "../Fetcher.ts" import { fetchAPI } from "../Fetcher.ts"
import { Failure } from "../api/failure.ts" import { Failure } from "../api/failure.ts"
import { getSession, saveSession } from "../api/session.ts" import { getSession, saveSession } from "../api/session.ts"
import "../style/form.css" import "../style/form.css"
import { useNavigate } from "react-router-dom" import { Link, useNavigate } from "react-router-dom"
export default function LoginApp() { export default function LoginApp() {
const [errors, setErrors] = useState<Failure[]>([]) const [errors, setErrors] = useState<Failure[]>([])
@ -22,13 +21,16 @@ export default function LoginApp() {
"auth/token", "auth/token",
{ email, password }, { email, password },
"POST", "POST",
false,
) )
if (response.ok) { if (response.ok) {
const session = getSession() const session = getSession()
const { token, expirationDate } = await response.json() const { token, expirationDate } = await response.json()
saveSession({ ...session, auth: { token, expirationDate }, urlTarget: undefined }) saveSession({
...session,
auth: { token, expirationDate },
urlTarget: undefined,
})
startTransition(() => { startTransition(() => {
navigate(session.urlTarget ?? "/") navigate(session.urlTarget ?? "/")
}) })
@ -83,12 +85,7 @@ export default function LoginApp() {
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="form-group"> <div className="form-group">
<label htmlFor="email">Email :</label> <label htmlFor="email">Email :</label>
<input <input type="text" id="email" name="email" required />
type="text"
id="email"
name="email"
required
/>
<label htmlFor="password">Mot de passe :</label> <label htmlFor="password">Mot de passe :</label>
<input <input
@ -98,9 +95,9 @@ export default function LoginApp() {
required required
/> />
<a href={BASE + "/register"} className="inscr"> <Link to={"register"} className="inscr">
Vous n'avez pas de compte ? Vous n'avez pas de compte ?
</a> </Link>
<br /> <br />
<br /> <br />
<div id="buttons"> <div id="buttons">

@ -44,7 +44,6 @@ function CourtKindButton({
image: string image: string
courtType: CourtType courtType: CourtType
}) { }) {
const navigate = useNavigate() const navigate = useNavigate()
return ( return (
@ -67,6 +66,12 @@ function CourtKindButton({
"POST", "POST",
) )
if (response.status === 401) {
// if unauthorized
navigate("/login")
return
}
const { id } = await response.json() const { id } = await response.json()
startTransition(() => { startTransition(() => {
navigate(`/tactic/${id}/edit`) navigate(`/tactic/${id}/edit`)

@ -1,11 +1,10 @@
import { FormEvent, startTransition, useRef, useState } from "react" import { FormEvent, startTransition, useRef, useState } from "react"
import { BASE } from "../Constants.ts"
import "../style/form.css" import "../style/form.css"
import { Failure } from "../api/failure.ts" import { Failure } from "../api/failure.ts"
import { fetchAPI } from "../Fetcher.ts" import { fetchAPI } from "../Fetcher.ts"
import { getSession, saveSession } from "../api/session.ts" import { getSession, saveSession } from "../api/session.ts"
import { useNavigate } from "react-router-dom" import { Link, useNavigate } from "react-router-dom"
export default function RegisterPage() { export default function RegisterPage() {
const usernameField = useRef<HTMLInputElement>(null) const usernameField = useRef<HTMLInputElement>(null)
@ -46,7 +45,11 @@ export default function RegisterPage() {
if (response.ok) { if (response.ok) {
const { token, expirationDate } = await response.json() const { token, expirationDate } = await response.json()
const session = getSession() const session = getSession()
saveSession({ ...session, auth: { token, expirationDate }, urlTarget: undefined }) saveSession({
...session,
auth: { token, expirationDate },
urlTarget: undefined,
})
startTransition(() => { startTransition(() => {
navigate(session.urlTarget ?? "/") navigate(session.urlTarget ?? "/")
}) })
@ -135,9 +138,9 @@ export default function RegisterPage() {
confidentialité de Sportify. confidentialité de Sportify.
</label> </label>
<a href={BASE + "/login"} className="inscr"> <Link to={"/login"} className="inscr">
Vous avez déjà un compte ? Vous avez déjà un compte ?
</a> </Link>
</div> </div>
<div id="buttons"> <div id="buttons">
<input <input

@ -15,9 +15,9 @@ export function Header() {
useEffect(() => { useEffect(() => {
async function loadUsername() { async function loadUsername() {
const response = await fetchAPIGet("user", false) const response = await fetchAPIGet("user")
if (response.status == 401) { if (response.status != 401) {
//if unauthorized //if unauthorized
return return
} }

Loading…
Cancel
Save