diff --git a/docs/contribution/governance.md b/docs/contribution/governance.md
index 80fb6e1..a5050d8 100644
--- a/docs/contribution/governance.md
+++ b/docs/contribution/governance.md
@@ -204,7 +204,7 @@ Open → Triaged → Accepted → In Progress → Review → Closed
#### Reporting Process
-1. **Contact**: Report violations to conduct@monynha.com
+1. **Contact**: Report violations to hello@monynha.com
2. **Investigation**: Core team investigates reports
3. **Resolution**: Appropriate action taken
4. **Communication**: Outcome communicated to involved parties
diff --git a/docs/repositories/_category_.json b/docs/repositories/_category_.json
new file mode 100644
index 0000000..d555d7b
--- /dev/null
+++ b/docs/repositories/_category_.json
@@ -0,0 +1,9 @@
+{
+ "label": "Repositories",
+ "position": 8,
+ "link": {
+ "type": "generated-index",
+ "description": "Discover repositories for the user marcelo-m7 and Monynha-Softwares organization (client-side).",
+ "slug": "/docs/repositories"
+ }
+}
diff --git a/docs/repositories/index.md b/docs/repositories/index.md
new file mode 100644
index 0000000..4691a1e
--- /dev/null
+++ b/docs/repositories/index.md
@@ -0,0 +1,12 @@
+---
+id: repositories-legacy
+slug: /docs/repositories/legacy
+title: Repositories (legacy)
+sidebar_position: 999
+---
+
+# Repositories (legacy)
+
+This legacy file has been replaced by a better client-side MDX page. See the primary page: [Repositories](/docs/repositories).
+
+If you are a maintainer and want to remove the legacy file, you can delete `docs/repositories/index.md` after confirming the new page works.
diff --git a/docs/repositories/index.mdx b/docs/repositories/index.mdx
new file mode 100644
index 0000000..960d613
--- /dev/null
+++ b/docs/repositories/index.mdx
@@ -0,0 +1,40 @@
+---
+title: Repositories
+sidebar_position: 8
+---
+
+import Repositories from '@site/src/components/Repositories';
+
+import React from 'react';
+
+# Repositories
+
+This page lists repositories discovered from the GitHub user `marcelo-m7` and the organization `Monynha-Softwares`. The fetching is done entirely in the browser using the public GitHub REST API.
+
+The page accepts optional query parameters in the URL:
+
+- `mode`: `user` (show only user repos), `org` (show only organization repos), or `both` (default)
+- `showForks`: `1` or `true` to include forked repos
+
+Examples:
+
+- `/docs/repositories?mode=user` — show only `marcelo-m7` repos
+- `/docs/repositories?mode=org&showForks=1` — show only `Monynha-Softwares` repos including forks
+
+export function RepositoryPageWrapper() {
+ const params = typeof window !== 'undefined' ? new URLSearchParams(window.location.search) : new URLSearchParams('');
+ const mode = params.get('mode') || 'both';
+ const showForks = params.get('showForks') === '1' || params.get('showForks') === 'true';
+ let user = 'marcelo-m7';
+ let org = 'Monynha-Softwares';
+ if (mode === 'org') user = null;
+ if (mode === 'user') org = null;
+ return ;
+}
+
+
+
+Notes:
+
+- Data is cached in `localStorage` for 6 hours to reduce requests and avoid hitting anonymous API rate limits.
+- If you need higher rate limits, provide a GitHub personal access token, but do not embed secrets in client-side code.
diff --git a/docusaurus.config.js b/docusaurus.config.js
index a30e1b6..cbddde6 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -98,6 +98,7 @@ const config = {
position: 'left',
items: [
{ to: '/docs/intro', label: 'Introduction' },
+ { to: '/docs/repositories', label: 'Repositories' },
{ to: '/docs/technologies/frontend-stack', label: 'Technologies' },
{ to: '/docs/guidelines/ux-guidelines', label: 'Guidelines' },
{ to: '/docs/identity/brand-guidelines', label: 'Identity' },
@@ -110,9 +111,9 @@ const config = {
label: 'Company',
position: 'right',
items: [
+ { label: 'Portfolio', href: 'https://marcelo.monynha.com/portfolio' },
+ { label: 'Projects', href: 'https://monynha.com/projects' },
{ label: 'Monynha.com', href: 'https://monynha.com' },
- { label: 'Projects', href: 'https://monynha.com/projetos' },
- { label: 'Portfolio', href: 'https://marcelo.monynha.com/portifolio' },
],
},
{
@@ -132,6 +133,10 @@ const config = {
label: 'Introduction',
to: '/docs/intro',
},
+ {
+ label: 'Repositories',
+ to: '/docs/repositories',
+ },
{
label: 'Projects',
to: '/docs/projects/boteco-pro',
@@ -151,11 +156,11 @@ const config = {
},
{
label: 'Projects',
- href: 'https://monynha.com/projetos',
+ href: 'https://monynha.com/projects',
},
{
label: 'Portfolio',
- href: 'https://marcelo.monynha.com/portifolio',
+ href: 'https://marcelo.monynha.com/portfolio',
},
],
},
diff --git a/sidebars.js b/sidebars.js
index 0db5c50..fff2f74 100644
--- a/sidebars.js
+++ b/sidebars.js
@@ -3,117 +3,94 @@
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
/**
- * Creating a sidebar enables you to:
- - create an ordered group of docs
- - render a sidebar for each doc of that group
- - provide next/previous navigation
+ * Dynamic sidebar generator.
+ *
+ * This file programmatically inspects the `docs/` directory and generates sidebar
+ * categories for top-level folders that contain documentation. It preserves the
+ * generated-index link pattern and tries to create deterministic ordering.
+ *
+ * If you need fully manual control over a specific category, replace or edit
+ * the generated block below.
+ */
- The sidebars can be generated from the filesystem, or explicitly defined here.
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
- Create as many sidebars as you want.
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const DOCS_DIR = path.join(__dirname, 'docs');
- @type {import('@docusaurus/plugin-content-docs').SidebarsConfig}
- */
-const sidebars = {
- // Custom sidebar with proper ordering
- tutorialSidebar: [
- 'intro',
- {
- type: 'category',
- label: 'Projects',
- link: {
- type: 'generated-index',
- description: 'Documentation for Monynha Softwares projects: overviews, tech stacks, and guides.',
- },
- items: [
- 'projects/boteco-pro',
- 'projects/artleo-creative-spaces/index',
- 'projects/boteco-pt/index',
- 'projects/facodi/index',
- 'projects/monynha-com/index',
- 'projects/monynha-online/index',
- 'projects/monynha-tech/index',
- 'projects/sweet-price/index'
- ]
- },
- {
- type: 'category',
- label: 'Technologies',
- link: {
- type: 'generated-index',
- description: 'Technology stack documentation covering frontend, backend, DevOps tools, and development practices.',
- },
- items: [
- 'technologies/frontend-stack',
- 'technologies/backend-stack',
- 'technologies/devops-tools'
- ]
- },
- {
- type: 'category',
- label: 'Guidelines',
- link: {
- type: 'generated-index',
- description: 'Development guidelines including UX principles, accessibility standards, code conventions, and security practices.',
- },
- items: [
- 'guidelines/ux-guidelines',
- 'guidelines/accessibility',
- 'guidelines/code-conventions',
- 'guidelines/security'
- ]
- },
- {
- type: 'category',
- label: 'Identity',
- link: {
- type: 'generated-index',
- description: 'Visual identity and UI component documentation including brand guidelines and component libraries.',
- },
- items: [
- 'identity/brand-guidelines',
- 'identity/ui-components'
- ]
- },
- {
- type: 'category',
- label: 'Contribution',
- link: {
- type: 'generated-index',
- description: 'Contribution guidelines, development workflow, and governance model for the Monynha Softwares community.',
- },
- items: [
- 'contribution/contributing',
- 'contribution/governance'
- ]
- },
- {
- type: 'category',
- label: 'Architecture',
- link: {
- type: 'generated-index',
- description: 'Technical architecture documentation covering monorepo structure, backend systems, and CI/CD pipelines.',
- },
- items: [
- 'architecture/monorepo-structure',
- 'architecture/backend-architecture',
- 'architecture/ci-cd'
- ]
+function readCategoryMeta(dir) {
+ try {
+ const p = path.join(DOCS_DIR, dir, '_category_.json');
+ if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, 'utf8'));
+ } catch (e) {
+ // ignore
+ }
+ return { label: dir.replace(/-/g, ' ').replace(/\b\w/g, (s) => s.toUpperCase()) };
+}
+
+function listDocsInDir(dir) {
+ const base = path.join(DOCS_DIR, dir);
+ if (!fs.existsSync(base)) return [];
+ const entries = fs.readdirSync(base, { withFileTypes: true });
+ const items = [];
+ for (const ent of entries) {
+ if (ent.name === '_category_.json') continue;
+ const full = path.join(base, ent.name);
+ if (ent.isFile() && /\.(md|mdx)$/.test(ent.name)) {
+ const id = `${dir}/${ent.name.replace(/\.(md|mdx)$/, '')}`;
+ items.push(id);
+ } else if (ent.isDirectory()) {
+ // prefer directory index.md/mdx
+ const idxMd = path.join(full, 'index.md');
+ const idxMdx = path.join(full, 'index.mdx');
+ if (fs.existsSync(idxMd) || fs.existsSync(idxMdx)) {
+ items.push(`${dir}/${ent.name}/index`);
+ } else {
+ const subfiles = fs.readdirSync(full).filter((n) => /\.(md|mdx)$/.test(n));
+ subfiles.forEach((sf) => items.push(`${dir}/${ent.name}/${sf.replace(/\.(md|mdx)$/, '')}`));
+ }
}
- ],
+ }
+ // deterministic ordering: index first, then alpha
+ items.sort((a, b) => {
+ if (a.endsWith('/index') && !b.endsWith('/index')) return -1;
+ if (b.endsWith('/index') && !a.endsWith('/index')) return 1;
+ return a.localeCompare(b);
+ });
+ return items;
+}
+
+// Top-level folders we want to expose in the sidebar (in desired order).
+const TOP_FOLDERS = [
+ 'projects',
+ 'repositories',
+ 'technologies',
+ 'guidelines',
+ 'identity',
+ 'contribution',
+ 'architecture',
+];
- // But you can create a sidebar manually
- /*
- tutorialSidebar: [
- 'intro',
- 'hello',
- {
- type: 'category',
- label: 'Tutorial',
- items: ['tutorial-basics/create-a-document'],
+const tutorialSidebar = ['intro'];
+
+for (const folder of TOP_FOLDERS) {
+ const items = listDocsInDir(folder);
+ if (items.length === 0) continue;
+ const meta = readCategoryMeta(folder);
+ tutorialSidebar.push({
+ type: 'category',
+ label: meta.label || folder,
+ link: {
+ type: 'generated-index',
+ description: (meta.link && meta.link.description) || `Documentation for ${meta.label || folder}`,
},
- ],
- */
-};
+ items,
+ });
+}
+
+const sidebars = { tutorialSidebar };
export default sidebars;
diff --git a/src/components/HomepageFeatures/index.js b/src/components/HomepageFeatures/index.js
index b89dd1a..d9dd722 100644
--- a/src/components/HomepageFeatures/index.js
+++ b/src/components/HomepageFeatures/index.js
@@ -4,32 +4,32 @@ import styles from './styles.module.css';
const FeatureList = [
{
- title: 'Projetos Inovadores',
+ title: 'Innovative Projects',
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
description: (
<>
- Explore nossa documentação completa de projetos, desde sistemas de gestão
- para bares e restaurantes até plataformas educacionais e soluções web modernas.
+ Browse our project documentation — from restaurant management systems to
+ educational platforms and modern web applications.
>
),
},
{
- title: 'Tecnologias Avançadas',
+ title: 'Advanced Technologies',
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
description: (
<>
- Conheça as tecnologias que utilizamos: TypeScript, Flutter, Convex, Coolify,
- Docker e muito mais. Guias práticos e melhores práticas incluídas.
+ Learn about the technologies we use: TypeScript, Flutter, Convex, Coolify,
+ Docker and more — with practical guides and recommended best practices.
>
),
},
{
- title: 'Padrões e Qualidade',
+ title: 'Standards & Quality',
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
description: (
<>
- Acesse nossos padrões internos de desenvolvimento, guias de contribuição
- e práticas recomendadas para manter a qualidade em todos os projetos.
+ Access our internal development standards, contribution guides, and
+ quality practices that help maintain excellence across projects.
>
),
},
diff --git a/src/components/Portfolio/index.js b/src/components/Portfolio/index.js
index 2c483fb..15d39be 100644
--- a/src/components/Portfolio/index.js
+++ b/src/components/Portfolio/index.js
@@ -6,27 +6,27 @@ import styles from './styles.module.css';
const projects = [
{
title: 'Boteco Pro',
- description: 'Sistema completo de gestão para bares e restaurantes com controle de pedidos, estoque e financeiro.',
+ description: 'A complete management system for bars and restaurants: orders, inventory, and billing.',
technologies: ['Flutter', 'TypeScript', 'Convex'],
- status: 'Em Desenvolvimento',
- link: '/docs/projects/boteco-pro',
+ status: 'In Development',
+ link: '/docs/projects/boteco-pro',
icon: '🍺',
color: '#FF6B35'
},
{
- title: 'Plataforma Educacional',
- description: 'Sistema de ensino online com cursos interativos, acompanhamento de progresso e certificação.',
+ title: 'Educational Platform',
+ description: 'Online learning platform with interactive courses, progress tracking, and certification.',
technologies: ['React', 'TypeScript', 'Docker'],
- status: 'Planejado',
+ status: 'Planned',
link: '/docs/intro',
icon: '🎓',
color: '#4F46E5'
},
{
- title: 'Sistema de Gestão Empresarial',
- description: 'Suite completa para gestão empresarial com módulos de RH, financeiro e operações.',
+ title: 'Enterprise Management Suite',
+ description: 'A complete suite for enterprise management with HR, finance, and operations modules.',
technologies: ['TypeScript', 'Coolify', 'Docker'],
- status: 'Em Planejamento',
+ status: 'Planned',
link: '/docs/intro',
icon: '🏢',
color: '#10B981'
@@ -35,15 +35,15 @@ const projects = [
const testimonials = [
{
- name: 'Cliente Satisfeito',
- role: 'Proprietário de Bar',
- content: 'O Boteco Pro revolucionou a gestão do meu estabelecimento. Interface intuitiva e funcionalidades completas.',
+ name: 'Happy Customer',
+ role: 'Bar Owner',
+ content: 'Boteco Pro transformed how I run my business. Intuitive UI and complete feature set.',
avatar: '👤'
},
{
- name: 'Parceiro Tecnológico',
- role: 'Desenvolvedor',
- content: 'Excelente trabalho em equipe e qualidade de código. Tecnologias modernas e boas práticas implementadas.',
+ name: 'Technology Partner',
+ role: 'Developer',
+ content: 'Great team and strong code quality. Modern technologies and solid practices in place.',
avatar: '👨💻'
}
];
@@ -132,14 +132,14 @@ export default function Portfolio() {
{/* CTA Section */}
-
Pronto para inovar com a gente?
-
Entre em contato e vamos discutir seu próximo projeto
+
Ready to innovate with us?
+
Get in touch and let's discuss your next project.
-
- Falar com a Equipe
+
+ Talk to the Team
- Ver Documentação
+ View Documentation
diff --git a/src/components/Repositories/index.jsx b/src/components/Repositories/index.jsx
new file mode 100644
index 0000000..3b89b9a
--- /dev/null
+++ b/src/components/Repositories/index.jsx
@@ -0,0 +1,194 @@
+import React, {useEffect, useState} from 'react';
+import styles from './styles.module.css';
+
+// Simple client-side GitHub repo fetcher with localStorage cache.
+// - Fetches repos for a user and an organization, merges and dedupes them.
+// - Uses unauthenticated GitHub API (rate limit: 60 req/hour per IP). Caches results to reduce requests.
+// - Fully client-side so it works on static sites.
+
+const CACHE_KEY = 'mona_repos_cache_v1';
+const CACHE_TTL_MS = 1000 * 60 * 60 * 6; // 6 hours
+
+async function fetchAllRepos(url) {
+ const perPage = 100;
+ let page = 1;
+ let results = [];
+ while (true) {
+ const resp = await fetch(`${url}${url.includes('?') ? '&' : '?'}per_page=${perPage}&page=${page}`);
+ if (!resp.ok) {
+ const text = await resp.text();
+ throw new Error(`GitHub API error ${resp.status}: ${text}`);
+ }
+ const data = await resp.json();
+ if (!Array.isArray(data)) break;
+ results = results.concat(data);
+ if (data.length < perPage) break;
+ page += 1;
+ }
+ return results;
+}
+
+function mergeAndDedupe(a, b) {
+ const map = new Map();
+ (a || []).concat(b || []).forEach((r) => map.set(r.full_name, r));
+ return Array.from(map.values()).sort((x, y) => new Date(y.updated_at) - new Date(x.updated_at));
+}
+
+export default function Repositories({user = 'marcelo-m7', org = 'Monynha-Softwares', showForks = false}) {
+ const [repos, setRepos] = useState(null);
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [cacheInfo, setCacheInfo] = useState(null);
+ const [refreshing, setRefreshing] = useState(false);
+ // trigger a forced reload by bumping this counter (used after clearing cache)
+ const [forceReloadCounter, setForceReloadCounter] = useState(0);
+
+ useEffect(() => {
+ let cancelled = false;
+
+ async function load({forceRefresh = false} = {}) {
+ setLoading(true);
+ setError(null);
+
+ try {
+ // Check cache unless forced
+ const cached = localStorage.getItem(CACHE_KEY);
+ if (!forceRefresh && cached) {
+ const parsed = JSON.parse(cached);
+ const age = Date.now() - parsed.ts;
+ setCacheInfo({ts: parsed.ts, age});
+ if (age < CACHE_TTL_MS) {
+ setRepos(parsed.data);
+ setLoading(false);
+ // still attempt background refresh but only if not forced
+ refreshInBackground(parsed.ts);
+ return;
+ }
+ }
+
+ const [userRepos, orgRepos] = await Promise.all([
+ fetchAllRepos(`https://api.github.com/users/${user}/repos?type=owner&sort=updated`),
+ fetchAllRepos(`https://api.github.com/orgs/${org}/repos?type=public&sort=updated`),
+ ]);
+
+ const merged = mergeAndDedupe(userRepos, orgRepos).filter((r) => showForks || !r.fork);
+ localStorage.setItem(CACHE_KEY, JSON.stringify({ts: Date.now(), data: merged}));
+ if (!cancelled) {
+ setRepos(merged);
+ setCacheInfo({ts: Date.now(), age: 0});
+ }
+ } catch (err) {
+ // If rate-limited or other errors, surface message but try to use stale cache
+ const cached = localStorage.getItem(CACHE_KEY);
+ if (cached) {
+ const parsed = JSON.parse(cached);
+ if (!cancelled) setRepos(parsed.data);
+ setError('Using cached data due to API error: ' + err.message);
+ } else {
+ setError(err.message);
+ }
+ } finally {
+ if (!cancelled) setLoading(false);
+ }
+ }
+
+ async function refreshInBackground(oldTs) {
+ try {
+ const [userRepos, orgRepos] = await Promise.all([
+ fetchAllRepos(`https://api.github.com/users/${user}/repos?type=owner&sort=updated`),
+ fetchAllRepos(`https://api.github.com/orgs/${org}/repos?type=public&sort=updated`),
+ ]);
+ const merged = mergeAndDedupe(userRepos, orgRepos).filter((r) => showForks || !r.fork);
+ localStorage.setItem(CACHE_KEY, JSON.stringify({ts: Date.now(), data: merged}));
+ // update UI only if there was no data yet
+ if (!repos) setRepos(merged);
+ setCacheInfo({ts: Date.now(), age: 0});
+ } catch (err) {
+ // ignore background errors
+ // console.warn('background refresh failed', err);
+ }
+ }
+
+ // Force refresh handler
+ async function refreshNow() {
+ setError(null);
+ try {
+ setRefreshing(true);
+ await load({forceRefresh: true});
+ } finally {
+ setRefreshing(false);
+ }
+ }
+
+ // Expose a small clear-cache helper by bumping a counter (causes effect to reload)
+ function clearCache() {
+ try {
+ localStorage.removeItem(CACHE_KEY);
+ } catch (e) {}
+ setCacheInfo(null);
+ setRepos(null);
+ // bump counter so effect re-runs and performs a fresh load
+ setForceReloadCounter((n) => n + 1);
+ }
+
+ load();
+ return () => { cancelled = true; };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [user, org, showForks, forceReloadCounter]);
+
+ if (loading && !repos) return ;
+ if (error && !repos) return ;
+
+ function renderCacheInfo() {
+ if (!cacheInfo) return null;
+ const seconds = Math.floor((Date.now() - cacheInfo.ts) / 1000);
+ const minutes = Math.floor(seconds / 60);
+ const hours = Math.floor(minutes / 60);
+ let when = '';
+ if (hours > 0) when = `${hours}h ${minutes % 60}m ago`;
+ else if (minutes > 0) when = `${minutes}m ago`;
+ else when = `${seconds}s ago`;
+
+ const ttlRemainingMs = Math.max(0, CACHE_TTL_MS - (Date.now() - cacheInfo.ts));
+ const ttlMinutes = Math.ceil(ttlRemainingMs / 60000);
+ return (
+
+
Cached: {when} • refresh in ~{ttlMinutes}m
+
+
+ {refreshing ? : null}
+ {refreshing ? 'Refreshing...' : 'Refresh now'}
+
+ { if (!loading) { clearCache(); } }} disabled={loading || refreshing} aria-label="Clear repositories cache">
+ Clear cache
+
+
+
+ );
+ }
+
+ return (
+
+ {renderCacheInfo()}
+ {error ?
⚠ {error}
: null}
+
+ {repos && repos.length === 0 &&
No repositories found.
}
+
+ );
+}
diff --git a/src/components/Repositories/styles.module.css b/src/components/Repositories/styles.module.css
new file mode 100644
index 0000000..91313ed
--- /dev/null
+++ b/src/components/Repositories/styles.module.css
@@ -0,0 +1,91 @@
+.container {
+ margin: 1rem 0;
+}
+.grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+ gap: 1rem;
+}
+.card {
+ display: flex;
+ flex-direction: column;
+ padding: 1rem;
+ border: 1px solid var(--ifm-card-border-color, #e5e7eb);
+ border-radius: 8px;
+ text-decoration: none;
+ color: inherit;
+ transition: transform 120ms ease, box-shadow 120ms ease;
+}
+.card:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 6px 18px rgba(0,0,0,0.06);
+}
+.cardHeader {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+.meta {
+ display: flex;
+ gap: 0.5rem;
+ font-size: 0.9rem;
+ color: var(--ifm-color-gray-600, #6b7280);
+}
+.description {
+ margin: 0.5rem 0 0.8rem 0;
+ color: var(--ifm-color-gray-700, #374151);
+}
+.footer {
+ margin-top: auto;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ color: var(--ifm-color-gray-500, #9ca3af);
+}
+.error { color: var(--ifm-color-danger, #b91c1c); }
+.warn { background: #fff7ed; padding: 0.5rem; border-radius: 4px; margin-bottom: 1rem; }
+
+.cacheBar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1rem;
+ gap: 0.5rem;
+}
+.cacheInfo { color: var(--ifm-color-gray-600, #6b7280); font-size: 0.95rem; }
+.refreshBtn {
+ background: var(--ifm-color-primary, #2563eb);
+ color: white;
+ border: none;
+ padding: 0.4rem 0.8rem;
+ border-radius: 6px;
+ cursor: pointer;
+}
+.refreshBtn:disabled { opacity: 0.6; cursor: not-allowed; }
+
+.cacheActions { display: flex; gap: 0.5rem; align-items: center; }
+.clearBtn {
+ background: transparent;
+ color: var(--ifm-color-gray-700, #374151);
+ border: 1px solid var(--ifm-color-muted, #e5e7eb);
+ padding: 0.35rem 0.7rem;
+ border-radius: 6px;
+ cursor: pointer;
+}
+.clearBtn:disabled { opacity: 0.6; cursor: not-allowed; }
+
+.spinner {
+ display: inline-block;
+ width: 14px;
+ height: 14px;
+ border: 2px solid rgba(255,255,255,0.25);
+ border-top-color: white;
+ border-radius: 50%;
+ margin-right: 0.5rem;
+ vertical-align: middle;
+ animation: spin 0.9s linear infinite;
+}
+
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
diff --git a/src/components/TechStack/index.js b/src/components/TechStack/index.js
index b8a69ee..e40711a 100644
--- a/src/components/TechStack/index.js
+++ b/src/components/TechStack/index.js
@@ -5,38 +5,44 @@ import styles from './styles.module.css';
const techStack = [
{
name: 'TypeScript',
+ slug: 'typescript',
icon: '🔷',
- description: 'Linguagem tipada para desenvolvimento robusto',
+ description: 'A typed superset of JavaScript for safer, scalable code',
color: '#3178c6'
},
{
name: 'Flutter',
+ slug: 'flutter',
icon: '📱',
- description: 'Framework para desenvolvimento mobile multiplataforma',
+ description: "Google's UI toolkit for building natively compiled apps",
color: '#02569B'
},
{
name: 'Convex',
+ slug: 'convex',
icon: '⚡',
- description: 'Backend-as-a-Service para aplicações realtime',
+ description: 'Realtime backend-as-a-service for modern apps',
color: '#FF6B35'
},
{
name: 'Coolify',
+ slug: 'coolify',
icon: '🚀',
- description: 'Plataforma de deploy e gerenciamento de aplicações',
+ description: 'Self-hosted deployment and app management platform',
color: '#4F46E5'
},
{
name: 'Docker',
+ slug: 'docker-compose',
icon: '🐳',
- description: 'Containerização para ambientes consistentes',
+ description: 'Containerization for consistent environments',
color: '#2496ED'
},
{
name: 'React',
+ slug: 'frontend-stack',
icon: '⚛️',
- description: 'Biblioteca para interfaces web interativas',
+ description: 'Library for building interactive web interfaces',
color: '#61DAFB'
}
];
@@ -61,16 +67,62 @@ function TechCard({ tech, isActive, onClick }) {
export default function TechStack() {
const [activeTech, setActiveTech] = useState(0);
+ const [previewHtml, setPreviewHtml] = useState(null);
+ const [previewLoading, setPreviewLoading] = useState(false);
+ const [previewError, setPreviewError] = useState(null);
+
+ async function fetchPreview(slug) {
+ if (!slug) return null;
+ setPreviewLoading(true);
+ setPreviewError(null);
+ try {
+ const res = await fetch(`/docs/technologies/${slug}/`);
+ if (!res.ok) throw new Error(`Preview fetch failed: ${res.status}`);
+ const text = await res.text();
+ const doc = new DOMParser().parseFromString(text, 'text/html');
+ const selectors = ['.theme-doc-markdown', '.markdown', 'article', 'main'];
+ for (const sel of selectors) {
+ const el = doc.querySelector(sel);
+ if (el) {
+ const p = el.querySelector('p');
+ if (p) {
+ return p.innerHTML;
+ }
+ }
+ }
+ // fallback: first paragraph in body
+ const p = doc.querySelector('p');
+ return p ? p.innerHTML : null;
+ } catch (err) {
+ setPreviewError(err.message);
+ return null;
+ } finally {
+ setPreviewLoading(false);
+ }
+ }
+
+ React.useEffect(() => {
+ let mounted = true;
+ setPreviewHtml(null);
+ setPreviewError(null);
+ const slug = techStack[activeTech]?.slug;
+ if (!slug) return undefined;
+ fetchPreview(slug).then((html) => {
+ if (mounted) setPreviewHtml(html);
+ });
+ return () => { mounted = false; };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [activeTech]);
return (
- Nossa Stack Tecnológica
+ Technologies & Stack
- Tecnologias modernas que impulsionam nossas soluções
+ Modern technologies that power our solutions
@@ -93,6 +145,16 @@ export default function TechStack() {
{techStack[activeTech].name}
{techStack[activeTech].description}
+
+ {previewLoading &&
Loading preview…
}
+ {previewError &&
Preview unavailable.
}
+ {previewHtml ? (
+
+ ) : null}
+
+
diff --git a/src/components/TechStack/styles.module.css b/src/components/TechStack/styles.module.css
index 205be0a..6a7f096 100644
--- a/src/components/TechStack/styles.module.css
+++ b/src/components/TechStack/styles.module.css
@@ -139,6 +139,40 @@
line-height: 1.5;
}
+.previewArea {
+ margin-top: 1rem;
+ border-top: 1px dashed var(--ifm-color-emphasis-200);
+ padding-top: 1rem;
+}
+
+.previewLoading {
+ color: var(--ifm-color-emphasis-500);
+ font-size: 0.95rem;
+}
+
+.previewError {
+ color: var(--ifm-color-danger-600);
+ font-size: 0.95rem;
+}
+
+.previewContent {
+ color: var(--ifm-color-emphasis-700);
+}
+
+.readMoreRow {
+ margin-top: 1rem;
+}
+
+.readMoreBtn {
+ display: inline-block;
+ background: var(--ifm-color-primary);
+ color: white;
+ padding: 0.5rem 0.75rem;
+ border-radius: 8px;
+ text-decoration: none;
+ font-weight: 600;
+}
+
@media screen and (max-width: 768px) {
.sectionTitle {
font-size: 2rem;
diff --git a/src/pages/index.js b/src/pages/index.js
index b9c8df8..32dba17 100644
--- a/src/pages/index.js
+++ b/src/pages/index.js
@@ -15,30 +15,30 @@ function HomepageHeader() {
);
@@ -48,8 +48,8 @@ export default function Home() {
const {siteConfig} = useDocusaurusContext();
return (
+ title={`MonaDocs - Monynha Softwares Documentation`}
+ description="Central documentation containing guides, standards, and information about Monynha Softwares projects, technologies, and processes">