Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 70 additions & 6 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,90 @@ jobs:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
- name: 📦 Checkout repository
uses: actions/checkout@v3

- name: Setup SSH agent
- name: 🔐 Setup SSH agent
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

- name: Setup known_hosts
- name: 🧾 Setup known_hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.VPS_KNOWN_HOST }}" > ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts

- name: Deploy
- name: 🚀 Deploy to VPS
run: |
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "ENVIRONMENT=PRODUCTION" >> $GITHUB_ENV
echo "🚀 Deploying PRODUCTION"
ssh ${{ secrets.VPS_USER }}@${{ secrets.VPS_HOST }} "bash /home/ubuntu/deploy-triptogether.sh"
ssh -o StrictHostKeyChecking=yes \
${{ secrets.VPS_USER }}@${{ secrets.VPS_HOST }} \
"bash /home/ubuntu/deploy-triptogether.sh"
else
echo "ENVIRONMENT=STAGING" >> $GITHUB_ENV
echo "🧪 Deploying STAGING"
ssh ${{ secrets.VPS_USER }}@${{ secrets.VPS_HOST }} "bash /home/ubuntu/deploy-triptogether-staging.sh"
ssh -o StrictHostKeyChecking=yes \
${{ secrets.VPS_USER }}@${{ secrets.VPS_HOST }} \
"bash /home/ubuntu/deploy-triptogether-staging.sh"
fi

############################################################
# ✅ SUCCESS NOTIFICATION (FULLY SECURED)
############################################################
- name: ✅ Telegram Success Notification
if: success()
env:
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
run: |
SAFE_COMMIT=$(printf "%s" "$COMMIT_MESSAGE" \
| sed -e 's/&/\&/g' \
-e 's/</\&lt;/g' \
-e 's/>/\&gt;/g')

curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
--data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
--data-urlencode "parse_mode=HTML" \
--data-urlencode "text=✅ <b>${ENVIRONMENT} Deploy SUCCESS</b>

📦 <b>Repo:</b> ${{ github.repository }}
🌿 <b>Branch:</b> ${{ github.ref_name }}
👤 <b>Author:</b> ${{ github.actor }}
📝 <b>Commit:</b> ${SAFE_COMMIT}
🕒 <b>Date:</b> $(date -u '+%Y-%m-%d %H:%M:%S UTC')

🔎 <a href='https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'>View Logs</a>"

############################################################
# ❌ FAILURE NOTIFICATION (FULLY SECURED)
############################################################
- name: ❌ Telegram Failure Notification
if: failure()
env:
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
run: |
SAFE_COMMIT=$(printf "%s" "$COMMIT_MESSAGE" \
| sed -e 's/&/\&amp;/g' \
-e 's/</\&lt;/g' \
-e 's/>/\&gt;/g')

curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
--data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
--data-urlencode "parse_mode=HTML" \
--data-urlencode "text=❌ <b>${ENVIRONMENT} Deploy FAILED</b>

📦 <b>Repo:</b> ${{ github.repository }}
🌿 <b>Branch:</b> ${{ github.ref_name }}
👤 <b>Author:</b> ${{ github.actor }}
📝 <b>Commit:</b> ${SAFE_COMMIT}
🕒 <b>Date:</b> $(date -u '+%Y-%m-%d %H:%M:%S UTC')

⚠️ <b>Check immediately.</b>

🔎 <a href='https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'>View Logs</a>"
147 changes: 97 additions & 50 deletions client/src/components/AddExpenseForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,31 @@ type AddExpenseFormProps = {

function AddExpenseForm({ tripId, members, onSuccess }: AddExpenseFormProps) {
const { auth } = useAuth();

const [title, setTitle] = useState("");
const [amount, setAmount] = useState("");
const [categoryId, setCategoryId] = useState("");
const [paidBy, setPaidBy] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);

if (!auth?.token) {
console.error("Utilisateur non authentifié");
setError("Utilisateur non authentifié.");
return;
}

if (!title || !amount || !categoryId || !paidBy) {
console.error("Champs manquants");
setError("Veuillez remplir tous les champs.");
return;
}

try {
setIsSubmitting(true);

const response = await fetch(
`${import.meta.env.VITE_API_URL}/api/expenses/${tripId}`,
{
Expand All @@ -58,59 +64,100 @@ function AddExpenseForm({ tripId, members, onSuccess }: AddExpenseFormProps) {
throw new Error(errorData.message || "Erreur création dépense");
}

// Reset
setTitle("");
setAmount("");
setCategoryId("");
setPaidBy("");

onSuccess();
} catch (error) {
console.error(error);
} catch (err: unknown) {
if (err instanceof Error) {
setError(err.message);
} else {
setError("Une erreur est survenue.");
}
} finally {
setIsSubmitting(false);
}
};

return (
<form className="add-expense-form" onSubmit={handleSubmit}>
<h2>Ajouter une dépense</h2>

<input
type="text"
placeholder="Titre"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
/>

<input
type="number"
placeholder="Montant"
value={amount}
onChange={(e) => setAmount(e.target.value)}
required
/>

<select
value={categoryId}
onChange={(e) => setCategoryId(e.target.value)}
required
>
<option value="">Choisir une catégorie</option>
<option value="1">Transport</option>
<option value="2">Nourriture</option>
<option value="3">Logement</option>
<option value="4">Autre</option>
<option value="5">Activité</option>
</select>

<select
value={paidBy}
onChange={(e) => setPaidBy(e.target.value)}
required
>
<option value="">Payé par</option>
{members.map((member) => (
<option key={member.id} value={member.id}>
{member.firstname || member.email}
</option>
))}
</select>

<button type="submit">Enregistrer</button>
<form className="add-expense-form" onSubmit={handleSubmit} noValidate>
<h2 id="add-expense-title">Ajouter une dépense</h2>

{error && (
<div className="form-error" role="alert">
{error}
</div>
)}

{/* Titre */}
<div className="form-group">
<label htmlFor="title">Titre</label>
<input
id="title"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
/>
</div>

{/* Montant */}
<div className="form-group">
<label htmlFor="amount">Montant (€)</label>
<input
id="amount"
type="number"
step="0.01"
min="0"
inputMode="decimal"
value={amount}
onChange={(e) => setAmount(e.target.value)}
required
/>
</div>

{/* Catégorie */}
<div className="form-group">
<label htmlFor="category">Catégorie</label>
<select
id="category"
value={categoryId}
onChange={(e) => setCategoryId(e.target.value)}
required
>
<option value="">Choisir une catégorie</option>
<option value="1">Transport</option>
<option value="2">Nourriture</option>
<option value="3">Logement</option>
<option value="4">Activité</option>
<option value="5">Autre</option>
</select>
</div>

{/* Payé par */}
<div className="form-group">
<label htmlFor="paidBy">Payé par</label>
<select
id="paidBy"
value={paidBy}
onChange={(e) => setPaidBy(e.target.value)}
required
>
<option value="">Sélectionner un membre</option>
{members.map((member) => (
<option key={member.id} value={member.id}>
{member.firstname || member.email}
</option>
))}
</select>
</div>

<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Enregistrement..." : "Enregistrer"}
</button>
</form>
);
}
Expand Down
Loading