feature/plausible #3

Merged
rui.moraes merged 2 commits from feature/plausible into main 2026-06-13 22:09:53 -03:00
10 changed files with 102 additions and 37 deletions
Showing only changes of commit 1c496e8d8d - Show all commits

2
app/.env Normal file
View File

@@ -0,0 +1,2 @@
VITE_PLAUSIBLE_API=http://localhost/api/event
VITE_PLAUSIBLE_DOMAIN=myapp-dev

2
app/.env.production Normal file
View File

@@ -0,0 +1,2 @@
VITE_PLAUSIBLE_API=https://plausible.mtst.tec.br/api/event
VITE_PLAUSIBLE_DOMAIN=https://decoda.mtst.tec.br

View File

@@ -3,7 +3,7 @@ FROM node:20-alpine AS builder
WORKDIR /app
ARG GIT_COMMIT_HASH=unknown
ARG APP_VERSION=1.1.2
ARG APP_VERSION=1.2.0
ENV VITE_APP_VERSION=$APP_VERSION
ENV VITE_GIT_HASH=$GIT_COMMIT_HASH

View File

@@ -1,14 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/puzzle.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="manifest" href="/manifest.json" />
<title>Decoda</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/puzzle.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="manifest" href="/manifest.json" />
<title>Decoda</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

View File

@@ -2,7 +2,7 @@
"name": "decoda",
"private": true,
"description": "Aplicação educacional desenvolvida para ensino de programação básica e letramento digital",
"version": "1.1.2",
"version": "1.2.0",
"main": "main.cjs",
"homepage": "./",
"type": "module",

View File

@@ -4,12 +4,13 @@
* @module App
*/
import { lazy, Suspense } from "react";
import { lazy, Suspense, useEffect } from "react";
import { HashRouter as Router, Routes, Route, useLocation } from "react-router-dom";
import "./App.css";
import HomePage from "./pages/HomePage/HomePage";
import LabPython from "./pages/LabPython/LabPython";
import ScrollToTop from "./components/ScrollToTop";
import { trackPageView } from "./services/plausible";
const Playground = lazy(() => import("./pages/Playground/Playground"));
const About = lazy(() => import("./pages/About/About"));
@@ -68,6 +69,10 @@ function AppRoutes() {
// keeps rendering behind the modal overlay.
const backgroundLocation = location.state?.backgroundLocation;
useEffect(() => {
trackPageView(location.pathname);
}, [location.pathname]);
return (
<>
<ScrollToTop />

View File

@@ -653,10 +653,6 @@ video {
bottom: 0px;
}
.bottom-0 {
bottom: 0px;
}
.bottom-10 {
bottom: 2.5rem;
}
@@ -984,10 +980,6 @@ video {
height: 0.25rem;
}
.h-1\.5 {
height: 0.375rem;
}
.h-10 {
height: 2.5rem;
}
@@ -2885,10 +2877,6 @@ video {
background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
}
.bg-gray-200\/50 {
background-color: rgb(229 231 235 / 0.5);
}
.bg-gray-300 {
--tw-bg-opacity: 1;
background-color: rgb(209 213 219 / var(--tw-bg-opacity, 1));
@@ -7125,10 +7113,6 @@ video {
transition-duration: 150ms;
}
.duration-1000 {
transition-duration: 1000ms;
}
.duration-150 {
transition-duration: 150ms;
}
@@ -7149,10 +7133,6 @@ video {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.ease-out {
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
.\[-webkit-text-stroke\:2px_black\] {
-webkit-text-stroke: 2px black;
}

View File

@@ -16,6 +16,7 @@ import React, {
import PropTypes from "prop-types";
import { gameEventBus } from "../utils/gameEvents";
import { GameProgressProvider, useGameProgress } from "./GameProgressContext";
import { trackEvent } from "../services/plausible";
export const GAME_STATES = {
PARADO: "parado",
@@ -89,6 +90,9 @@ function GameStateInnerProvider({ children, gameConfig }) {
);
return urlParams.get(debugKey)?.toLowerCase() === "true";
});
const [phaseStartTime, setPhaseStartTime] = useState(null);
const [attemptCount, setAttemptCount] = useState(0);
const [failureCount, setFailureCount] = useState(0);
const getCodeFromWorkspace = useRef(null);
const getCodeFromEditor = useRef(null);
@@ -103,6 +107,8 @@ function GameStateInnerProvider({ children, gameConfig }) {
* @throws {console.error} Se editor não registrou a função de execução
*/
const execute = () => {
setAttemptCount(prev => prev + 1);
if (editorType === "code") {
if (getCodeFromEditor.current) {
const codigo = getCodeFromEditor.current();
@@ -145,6 +151,19 @@ function GameStateInnerProvider({ children, gameConfig }) {
if (!completedPhases.includes(currentPhase)) {
setCompletedPhases([...completedPhases, currentPhase]);
}
const durationSeconds = phaseStartTime
? Math.round((Date.now() - phaseStartTime) / 1000)
: null;
trackEvent('Activity Success', {
activity: gameConfig.gameId,
phase: currentPhase,
blocks: currentBlockCount,
attempts: attemptCount,
failures: failureCount,
durationSeconds,
});
};
/**
@@ -157,6 +176,14 @@ function GameStateInnerProvider({ children, gameConfig }) {
*/
const finalizeWithFailure = () => {
setExecutionState(GAME_STATES.FALHA);
setFailureCount(prev => prev + 1);
trackEvent('Activity Failure', {
activity: gameConfig.gameId,
phase: currentPhase,
blocks: currentBlockCount,
error: failureMessage,
});
};
/**
@@ -198,6 +225,9 @@ function GameStateInnerProvider({ children, gameConfig }) {
setGeneratedCode("");
setCurrentBlockCount(0);
setCodeEditorContent("");
setPhaseStartTime(Date.now());
setAttemptCount(0);
setFailureCount(0);
};
/**
@@ -317,6 +347,8 @@ function GameStateInnerProvider({ children, gameConfig }) {
setFailureMessage,
isDebugMode,
setIsDebugMode,
attemptCount,
failureCount,
}}
>
{children}

View File

@@ -0,0 +1,41 @@
const PLAUSIBLE_API = import.meta.env.VITE_PLAUSIBLE_API || 'http://localhost/api/event';
const DOMAIN = import.meta.env.VITE_PLAUSIBLE_DOMAIN || 'myapp-dev';
const sendEvent = (payload) => {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
fetch(PLAUSIBLE_API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
signal: controller.signal,
})
.catch((e) => console.error('Plausible error:', e))
.finally(() => clearTimeout(timeout));
};
export const trackPageView = (pathname) => {
if (!pathname) return;
sendEvent({
domain: DOMAIN,
url: `${window.location.origin}${pathname}`,
referrer: document.referrer,
screenWidth: window.innerWidth,
name: 'pageview',
});
};
export const trackEvent = (eventName, properties = {}) => {
if (!eventName) return;
sendEvent({
domain: DOMAIN,
url: window.location.href,
referrer: document.referrer,
screenWidth: window.innerWidth,
name: eventName,
props: properties,
});
};

View File

@@ -74,6 +74,9 @@ export default defineConfig({
"@": path.resolve(__dirname, "./src"),
},
},
server: {
allowedHosts: ["localhost", "dev.local", "decoda.mtst.tec.br"],
},
plugins: [
react(),
copyLetramentoAtividades(),