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

Loading repositories...

; + if (error && !repos) return

Error: {error}

; + + 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
+
+ + +
+
+ ); + } + + return ( +
+ {renderCacheInfo()} + {error ?
⚠ {error}
: null} +
+ {(repos || []).map((r) => ( + +
+

{r.name}

+
+ ★ {r.stargazers_count} + ● {r.language || '—'} +
+
+

{r.description}

+
+ Updated {new Date(r.updated_at).toLocaleString()} +
+
+ ))} +
+ {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() {
- Bem-vindo à MonaDocs - -

- Documentação central da Monynha Softwares - Inovando com tecnologia -

-
-

- Explore nossa documentação completa sobre projetos inovadores, - tecnologias avançadas e padrões de desenvolvimento que impulsionam - soluções digitais de ponta. + Welcome to MonaDocs + +

+ Central documentation for Monynha Softwares — innovating with technology

-
-
- - 🚀 Explorar Documentação - - - 🌐 Visitar Website - -
+
+

+ Explore our complete documentation on innovative projects, modern + technologies, and engineering standards that power robust digital + solutions. +

+
+
+ + 🚀 Explore Documentation + + + 🌐 Visit Website + +
); @@ -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">