feat: complete Syndarix rebranding from PragmaStack

- Update PROJECT_NAME to Syndarix in backend config
- Update all frontend components with Syndarix branding
- Replace all GitHub URLs with Gitea Syndarix repo URLs
- Update metadata, headers, footers with new branding
- Update tests to match new URLs
- Update E2E tests for new repo references
- Preserve "Built on PragmaStack" attribution in docs

Closes #13

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-29 13:30:45 +01:00
parent 6e3cdebbfb
commit ebd307cab4
38 changed files with 159 additions and 146 deletions

View File

@@ -1,6 +1,6 @@
# PragmaStack Backend API # Syndarix Backend API
> The pragmatic, production-ready FastAPI backend for PragmaStack. > The pragmatic, production-ready FastAPI backend for Syndarix.
## Overview ## Overview

View File

@@ -5,7 +5,7 @@ from pydantic_settings import BaseSettings
class Settings(BaseSettings): class Settings(BaseSettings):
PROJECT_NAME: str = "PragmaStack" PROJECT_NAME: str = "Syndarix"
VERSION: str = "1.0.0" VERSION: str = "1.0.0"
API_V1_STR: str = "/api/v1" API_V1_STR: str = "/api/v1"

View File

@@ -1,4 +1,4 @@
# PragmaStack - Frontend # Syndarix - Frontend
Production-ready Next.js 16 frontend with TypeScript, authentication, admin panel, and internationalization. Production-ready Next.js 16 frontend with TypeScript, authentication, admin panel, and internationalization.

View File

@@ -273,7 +273,7 @@ NEXT_PUBLIC_DEMO_MODE=true npm run dev
**1. Fork Repository** **1. Fork Repository**
```bash ```bash
gh repo fork your-repo/fast-next-template git clone https://gitea.pragmazest.com/cardosofelipe/syndarix.git
``` ```
**2. Connect to Vercel** **2. Connect to Vercel**

View File

@@ -1,6 +1,6 @@
# Internationalization (i18n) Guide # Internationalization (i18n) Guide
This document describes the internationalization implementation in the PragmaStack. This document describes the internationalization implementation in Syndarix.
## Overview ## Overview

View File

@@ -4,10 +4,10 @@
## Logo ## Logo
The **PragmaStack** logo represents the core values of the project: structure, speed, and clarity. The **Syndarix** logo represents the core values of the project: structure, speed, and clarity.
<div align="center"> <div align="center">
<img src="../../public/logo.svg" alt="PragmaStack Logo" width="300" /> <img src="../../public/logo.svg" alt="Syndarix Logo" width="300" />
<p><em>The Stack: Geometric layers representing the full-stack architecture.</em></p> <p><em>The Stack: Geometric layers representing the full-stack architecture.</em></p>
</div> </div>
@@ -16,7 +16,7 @@ The **PragmaStack** logo represents the core values of the project: structure, s
For smaller contexts (favicons, headers), we use the simplified icon: For smaller contexts (favicons, headers), we use the simplified icon:
<div align="center"> <div align="center">
<img src="../../public/logo-icon.svg" alt="PragmaStack Icon" width="64" /> <img src="../../public/logo-icon.svg" alt="Syndarix Icon" width="64" />
</div> </div>
For now, we use the **Lucide React** icon set for all iconography. Icons should be used sparingly and meaningfully to enhance understanding, not just for decoration. For now, we use the **Lucide React** icon set for all iconography. Icons should be used sparingly and meaningfully to enhance understanding, not just for decoration.

View File

@@ -1,6 +1,6 @@
# Branding Guidelines # Branding Guidelines
Welcome to the **PragmaStack** branding guidelines. This section defines who we are, how we speak, and how we look. Welcome to the **Syndarix** branding guidelines. This section defines who we are, how we speak, and how we look.
## Contents ## Contents

View File

@@ -1,6 +1,6 @@
# Quick Start Guide # Quick Start Guide
Get up and running with the PragmaStack design system immediately. This guide covers the essential patterns you need to build 80% of interfaces. Get up and running with the Syndarix design system immediately. This guide covers the essential patterns you need to build 80% of interfaces.
--- ---

View File

@@ -1,6 +1,6 @@
# AI Code Generation Guidelines # AI Code Generation Guidelines
**For AI Assistants**: This document contains strict rules for generating code in the PragmaStack project. Follow these rules to ensure generated code matches the design system perfectly. **For AI Assistants**: This document contains strict rules for generating code in the Syndarix project. Follow these rules to ensure generated code matches the design system perfectly.
--- ---

View File

@@ -1,6 +1,6 @@
# Quick Reference # Quick Reference
**Bookmark this page** for instant lookups of colors, spacing, typography, components, and common patterns. Your go-to cheat sheet for the PragmaStack design system. **Bookmark this page** for instant lookups of colors, spacing, typography, components, and common patterns. Your go-to cheat sheet for the Syndarix design system.
--- ---

View File

@@ -1,6 +1,6 @@
# Design System Documentation # Design System Documentation
**PragmaStack Design System** - A comprehensive guide to building consistent, accessible, and beautiful user interfaces. **Syndarix Design System** - A comprehensive guide to building consistent, accessible, and beautiful user interfaces.
--- ---

View File

@@ -14,7 +14,7 @@ test.describe('Homepage - Desktop Navigation', () => {
test('should display header with logo and navigation', async ({ page }) => { test('should display header with logo and navigation', async ({ page }) => {
// Logo should be visible // Logo should be visible
await expect(page.getByRole('link', { name: /PragmaStack/i })).toBeVisible(); await expect(page.getByRole('link', { name: /Syndarix/i })).toBeVisible();
// Desktop navigation links should be visible (use locator to find within header) // Desktop navigation links should be visible (use locator to find within header)
const header = page.locator('header').first(); const header = page.locator('header').first();
@@ -23,8 +23,8 @@ test.describe('Homepage - Desktop Navigation', () => {
}); });
test('should display GitHub link with star badge', async ({ page }) => { test('should display GitHub link with star badge', async ({ page }) => {
// Find GitHub link by checking for one that has github.com in href // Find GitHub link by checking for one that has gitea.pragmazest.com in href
const githubLink = page.locator('a[href*="github.com"]').first(); const githubLink = page.locator('a[href*="gitea.pragmazest.com"]').first();
await expect(githubLink).toBeVisible(); await expect(githubLink).toBeVisible();
await expect(githubLink).toHaveAttribute('target', '_blank'); await expect(githubLink).toHaveAttribute('target', '_blank');
}); });
@@ -120,7 +120,7 @@ test.describe('Homepage - Hero Section', () => {
test('should navigate to GitHub when clicking View on GitHub', async ({ page }) => { test('should navigate to GitHub when clicking View on GitHub', async ({ page }) => {
const githubLink = page.getByRole('link', { name: /View on GitHub/i }).first(); const githubLink = page.getByRole('link', { name: /View on GitHub/i }).first();
await expect(githubLink).toBeVisible(); await expect(githubLink).toBeVisible();
await expect(githubLink).toHaveAttribute('href', expect.stringContaining('github.com')); await expect(githubLink).toHaveAttribute('href', expect.stringContaining('gitea.pragmazest.com'));
}); });
test('should navigate to components when clicking Explore Components', async ({ page }) => { test('should navigate to components when clicking Explore Components', async ({ page }) => {
@@ -250,7 +250,7 @@ test.describe('Homepage - Feature Sections', () => {
}); });
test('should display philosophy section', async ({ page }) => { test('should display philosophy section', async ({ page }) => {
await expect(page.getByRole('heading', { name: /Why PragmaStack/i })).toBeVisible(); await expect(page.getByRole('heading', { name: /Why Syndarix/i })).toBeVisible();
await expect(page.getByText(/MIT licensed/i).first()).toBeVisible(); await expect(page.getByText(/MIT licensed/i).first()).toBeVisible();
}); });
}); });
@@ -264,7 +264,7 @@ test.describe('Homepage - Footer', () => {
// Scroll to footer // Scroll to footer
await page.locator('footer').scrollIntoViewIfNeeded(); await page.locator('footer').scrollIntoViewIfNeeded();
await expect(page.getByText(/PragmaStack. MIT Licensed/i)).toBeVisible(); await expect(page.getByText(/Syndarix. MIT Licensed/i)).toBeVisible();
}); });
}); });
@@ -285,7 +285,7 @@ test.describe('Homepage - Accessibility', () => {
}); });
test('should have accessible links with proper attributes', async ({ page }) => { test('should have accessible links with proper attributes', async ({ page }) => {
const githubLink = page.locator('a[href*="github.com"]').first(); const githubLink = page.locator('a[href*="gitea.pragmazest.com"]').first();
await expect(githubLink).toHaveAttribute('target', '_blank'); await expect(githubLink).toHaveAttribute('target', '_blank');
await expect(githubLink).toHaveAttribute('rel', 'noopener noreferrer'); await expect(githubLink).toHaveAttribute('rel', 'noopener noreferrer');
}); });

View File

@@ -7,42 +7,42 @@
* - Please do NOT modify this file. * - Please do NOT modify this file.
*/ */
const PACKAGE_VERSION = '2.12.3'; const PACKAGE_VERSION = '2.12.3'
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'; const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse'); const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set(); const activeClientIds = new Set()
addEventListener('install', function () { addEventListener('install', function () {
self.skipWaiting(); self.skipWaiting()
}); })
addEventListener('activate', function (event) { addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim()); event.waitUntil(self.clients.claim())
}); })
addEventListener('message', async function (event) { addEventListener('message', async function (event) {
const clientId = Reflect.get(event.source || {}, 'id'); const clientId = Reflect.get(event.source || {}, 'id')
if (!clientId || !self.clients) { if (!clientId || !self.clients) {
return; return
} }
const client = await self.clients.get(clientId); const client = await self.clients.get(clientId)
if (!client) { if (!client) {
return; return
} }
const allClients = await self.clients.matchAll({ const allClients = await self.clients.matchAll({
type: 'window', type: 'window',
}); })
switch (event.data) { switch (event.data) {
case 'KEEPALIVE_REQUEST': { case 'KEEPALIVE_REQUEST': {
sendToClient(client, { sendToClient(client, {
type: 'KEEPALIVE_RESPONSE', type: 'KEEPALIVE_RESPONSE',
}); })
break; break
} }
case 'INTEGRITY_CHECK_REQUEST': { case 'INTEGRITY_CHECK_REQUEST': {
@@ -52,12 +52,12 @@ addEventListener('message', async function (event) {
packageVersion: PACKAGE_VERSION, packageVersion: PACKAGE_VERSION,
checksum: INTEGRITY_CHECKSUM, checksum: INTEGRITY_CHECKSUM,
}, },
}); })
break; break
} }
case 'MOCK_ACTIVATE': { case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId); activeClientIds.add(clientId)
sendToClient(client, { sendToClient(client, {
type: 'MOCKING_ENABLED', type: 'MOCKING_ENABLED',
@@ -67,51 +67,54 @@ addEventListener('message', async function (event) {
frameType: client.frameType, frameType: client.frameType,
}, },
}, },
}); })
break; break
} }
case 'CLIENT_CLOSED': { case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId); activeClientIds.delete(clientId)
const remainingClients = allClients.filter((client) => { const remainingClients = allClients.filter((client) => {
return client.id !== clientId; return client.id !== clientId
}); })
// Unregister itself when there are no more clients // Unregister itself when there are no more clients
if (remainingClients.length === 0) { if (remainingClients.length === 0) {
self.registration.unregister(); self.registration.unregister()
} }
break; break
} }
} }
}); })
addEventListener('fetch', function (event) { addEventListener('fetch', function (event) {
const requestInterceptedAt = Date.now(); const requestInterceptedAt = Date.now()
// Bypass navigation requests. // Bypass navigation requests.
if (event.request.mode === 'navigate') { if (event.request.mode === 'navigate') {
return; return
} }
// Opening the DevTools triggers the "only-if-cached" request // Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests. // that cannot be handled by the worker. Bypass such requests.
if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') { if (
return; event.request.cache === 'only-if-cached' &&
event.request.mode !== 'same-origin'
) {
return
} }
// Bypass all requests when there are no active clients. // Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests // Prevents the self-unregistered worked from handling requests
// after it's been terminated (still remains active until the next reload). // after it's been terminated (still remains active until the next reload).
if (activeClientIds.size === 0) { if (activeClientIds.size === 0) {
return; return
} }
const requestId = crypto.randomUUID(); const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId, requestInterceptedAt)); event.respondWith(handleRequest(event, requestId, requestInterceptedAt))
}); })
/** /**
* @param {FetchEvent} event * @param {FetchEvent} event
@@ -119,18 +122,23 @@ addEventListener('fetch', function (event) {
* @param {number} requestInterceptedAt * @param {number} requestInterceptedAt
*/ */
async function handleRequest(event, requestId, requestInterceptedAt) { async function handleRequest(event, requestId, requestInterceptedAt) {
const client = await resolveMainClient(event); const client = await resolveMainClient(event)
const requestCloneForEvents = event.request.clone(); const requestCloneForEvents = event.request.clone()
const response = await getResponse(event, client, requestId, requestInterceptedAt); const response = await getResponse(
event,
client,
requestId,
requestInterceptedAt,
)
// Send back the response clone for the "response:*" life-cycle events. // Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise // Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely. // this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) { if (client && activeClientIds.has(client.id)) {
const serializedRequest = await serializeRequest(requestCloneForEvents); const serializedRequest = await serializeRequest(requestCloneForEvents)
// Clone the response so both the client and the library could consume it. // Clone the response so both the client and the library could consume it.
const responseClone = response.clone(); const responseClone = response.clone()
sendToClient( sendToClient(
client, client,
@@ -151,11 +159,11 @@ async function handleRequest(event, requestId, requestInterceptedAt) {
}, },
}, },
}, },
responseClone.body ? [serializedRequest.body, responseClone.body] : [] responseClone.body ? [serializedRequest.body, responseClone.body] : [],
); )
} }
return response; return response
} }
/** /**
@@ -167,30 +175,30 @@ async function handleRequest(event, requestId, requestInterceptedAt) {
* @returns {Promise<Client | undefined>} * @returns {Promise<Client | undefined>}
*/ */
async function resolveMainClient(event) { async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId); const client = await self.clients.get(event.clientId)
if (activeClientIds.has(event.clientId)) { if (activeClientIds.has(event.clientId)) {
return client; return client
} }
if (client?.frameType === 'top-level') { if (client?.frameType === 'top-level') {
return client; return client
} }
const allClients = await self.clients.matchAll({ const allClients = await self.clients.matchAll({
type: 'window', type: 'window',
}); })
return allClients return allClients
.filter((client) => { .filter((client) => {
// Get only those clients that are currently visible. // Get only those clients that are currently visible.
return client.visibilityState === 'visible'; return client.visibilityState === 'visible'
}) })
.find((client) => { .find((client) => {
// Find the client ID that's recorded in the // Find the client ID that's recorded in the
// set of clients that have registered the worker. // set of clients that have registered the worker.
return activeClientIds.has(client.id); return activeClientIds.has(client.id)
}); })
} }
/** /**
@@ -203,34 +211,36 @@ async function resolveMainClient(event) {
async function getResponse(event, client, requestId, requestInterceptedAt) { async function getResponse(event, client, requestId, requestInterceptedAt) {
// Clone the request because it might've been already used // Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client). // (i.e. its body has been read and sent to the client).
const requestClone = event.request.clone(); const requestClone = event.request.clone()
function passthrough() { function passthrough() {
// Cast the request headers to a new Headers instance // Cast the request headers to a new Headers instance
// so the headers can be manipulated with. // so the headers can be manipulated with.
const headers = new Headers(requestClone.headers); const headers = new Headers(requestClone.headers)
// Remove the "accept" header value that marked this request as passthrough. // Remove the "accept" header value that marked this request as passthrough.
// This prevents request alteration and also keeps it compliant with the // This prevents request alteration and also keeps it compliant with the
// user-defined CORS policies. // user-defined CORS policies.
const acceptHeader = headers.get('accept'); const acceptHeader = headers.get('accept')
if (acceptHeader) { if (acceptHeader) {
const values = acceptHeader.split(',').map((value) => value.trim()); const values = acceptHeader.split(',').map((value) => value.trim())
const filteredValues = values.filter((value) => value !== 'msw/passthrough'); const filteredValues = values.filter(
(value) => value !== 'msw/passthrough',
)
if (filteredValues.length > 0) { if (filteredValues.length > 0) {
headers.set('accept', filteredValues.join(', ')); headers.set('accept', filteredValues.join(', '))
} else { } else {
headers.delete('accept'); headers.delete('accept')
} }
} }
return fetch(requestClone, { headers }); return fetch(requestClone, { headers })
} }
// Bypass mocking when the client is not active. // Bypass mocking when the client is not active.
if (!client) { if (!client) {
return passthrough(); return passthrough()
} }
// Bypass initial page load requests (i.e. static assets). // Bypass initial page load requests (i.e. static assets).
@@ -238,11 +248,11 @@ async function getResponse(event, client, requestId, requestInterceptedAt) {
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests. // and is not ready to handle requests.
if (!activeClientIds.has(client.id)) { if (!activeClientIds.has(client.id)) {
return passthrough(); return passthrough()
} }
// Notify the client that a request has been intercepted. // Notify the client that a request has been intercepted.
const serializedRequest = await serializeRequest(event.request); const serializedRequest = await serializeRequest(event.request)
const clientMessage = await sendToClient( const clientMessage = await sendToClient(
client, client,
{ {
@@ -253,20 +263,20 @@ async function getResponse(event, client, requestId, requestInterceptedAt) {
...serializedRequest, ...serializedRequest,
}, },
}, },
[serializedRequest.body] [serializedRequest.body],
); )
switch (clientMessage.type) { switch (clientMessage.type) {
case 'MOCK_RESPONSE': { case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data); return respondWithMock(clientMessage.data)
} }
case 'PASSTHROUGH': { case 'PASSTHROUGH': {
return passthrough(); return passthrough()
} }
} }
return passthrough(); return passthrough()
} }
/** /**
@@ -277,18 +287,21 @@ async function getResponse(event, client, requestId, requestInterceptedAt) {
*/ */
function sendToClient(client, message, transferrables = []) { function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const channel = new MessageChannel(); const channel = new MessageChannel()
channel.port1.onmessage = (event) => { channel.port1.onmessage = (event) => {
if (event.data && event.data.error) { if (event.data && event.data.error) {
return reject(event.data.error); return reject(event.data.error)
} }
resolve(event.data); resolve(event.data)
}; }
client.postMessage(message, [channel.port2, ...transferrables.filter(Boolean)]); client.postMessage(message, [
}); channel.port2,
...transferrables.filter(Boolean),
])
})
} }
/** /**
@@ -301,17 +314,17 @@ function respondWithMock(response) {
// instance will have status code set to 0. Since it's not possible to create // instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately. // a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) { if (response.status === 0) {
return Response.error(); return Response.error()
} }
const mockedResponse = new Response(response.body, response); const mockedResponse = new Response(response.body, response)
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true, value: true,
enumerable: true, enumerable: true,
}); })
return mockedResponse; return mockedResponse
} }
/** /**
@@ -332,5 +345,5 @@ async function serializeRequest(request) {
referrerPolicy: request.referrerPolicy, referrerPolicy: request.referrerPolicy,
body: await request.arrayBuffer(), body: await request.arrayBuffer(),
keepalive: request.keepalive, keepalive: request.keepalive,
}; }
} }

View File

@@ -10,7 +10,7 @@ import { Footer } from '@/components/layout/Footer';
export const metadata: Metadata = { export const metadata: Metadata = {
title: { title: {
template: '%s | PragmaStack', template: '%s | Syndarix',
default: 'Dashboard', default: 'Dashboard',
}, },
}; };

View File

@@ -12,7 +12,7 @@ import { AdminSidebar, Breadcrumbs } from '@/components/admin';
export const metadata: Metadata = { export const metadata: Metadata = {
title: { title: {
template: '%s | Admin | PragmaStack', template: '%s | Admin | Syndarix',
default: 'Admin Dashboard', default: 'Admin Dashboard',
}, },
}; };

View File

@@ -26,8 +26,8 @@ import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Demo Tour | PragmaStack', title: 'Demo Tour | Syndarix',
description: 'Try all features with demo credentials - comprehensive guide to the PragmaStack', description: 'Try all features with demo credentials - comprehensive guide to the Syndarix',
}; };
const demoCategories = [ const demoCategories = [

View File

@@ -120,7 +120,7 @@ export default function DocsHub() {
<h2 className="text-4xl font-bold tracking-tight mb-4">Design System Documentation</h2> <h2 className="text-4xl font-bold tracking-tight mb-4">Design System Documentation</h2>
<p className="text-lg text-muted-foreground mb-8"> <p className="text-lg text-muted-foreground mb-8">
Comprehensive guides, best practices, and references for building consistent, Comprehensive guides, best practices, and references for building consistent,
accessible, and maintainable user interfaces with the PragmaStack design system. accessible, and maintainable user interfaces with the Syndarix design system.
</p> </p>
<div className="flex flex-wrap gap-3 justify-center"> <div className="flex flex-wrap gap-3 justify-center">
<Link href="/dev/docs/design-system/00-quick-start"> <Link href="/dev/docs/design-system/00-quick-start">

View File

@@ -14,7 +14,7 @@ import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Design System Hub | PragmaStack', title: 'Design System Hub | Syndarix',
description: description:
'Interactive design system demonstrations with live examples - explore components, layouts, spacing, and forms built with shadcn/ui and Tailwind CSS', 'Interactive design system demonstrations with live examples - explore components, layouts, spacing, and forms built with shadcn/ui and Tailwind CSS',
}; };
@@ -90,7 +90,7 @@ export default function DesignSystemHub() {
</div> </div>
<p className="text-lg text-muted-foreground"> <p className="text-lg text-muted-foreground">
Interactive demonstrations, live examples, and comprehensive documentation for the Interactive demonstrations, live examples, and comprehensive documentation for the
PragmaStack design system. Built with shadcn/ui + Tailwind CSS 4. Syndarix design system. Built with shadcn/ui + Tailwind CSS 4.
</p> </p>
</div> </div>
</div> </div>

View File

@@ -1,7 +1,7 @@
/* istanbul ignore file -- @preserve Landing page with complex interactions tested via E2E */ /* istanbul ignore file -- @preserve Landing page with complex interactions tested via E2E */
/** /**
* Homepage / Landing Page * Homepage / Landing Page
* Main landing page for the PragmaStack project * Main landing page for the Syndarix project
* Showcases features, tech stack, and provides demos for developers * Showcases features, tech stack, and provides demos for developers
*/ */
@@ -68,7 +68,7 @@ export default function Home() {
<div className="container mx-auto px-6 py-8"> <div className="container mx-auto px-6 py-8">
<div className="flex flex-col md:flex-row items-center justify-between gap-4"> <div className="flex flex-col md:flex-row items-center justify-between gap-4">
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
© {new Date().getFullYear()} PragmaStack. MIT Licensed. © {new Date().getFullYear()} Syndarix. MIT Licensed.
</div> </div>
<div className="flex items-center gap-6 text-sm text-muted-foreground"> <div className="flex items-center gap-6 text-sm text-muted-foreground">
<Link href="/demos" className="hover:text-foreground transition-colors"> <Link href="/demos" className="hover:text-foreground transition-colors">

View File

@@ -1,7 +1,7 @@
@import 'tailwindcss'; @import 'tailwindcss';
/** /**
* PragmaStack Design System * Syndarix Design System
* Theme: Modern Minimal (from tweakcn.com) * Theme: Modern Minimal (from tweakcn.com)
* Primary: Blue | Color Space: OKLCH * Primary: Blue | Color Space: OKLCH
* *

View File

@@ -96,12 +96,12 @@ export function DevLayout({ children }: DevLayoutProps) {
<div className="flex items-center gap-3 shrink-0"> <div className="flex items-center gap-3 shrink-0">
<Image <Image
src="/logo-icon.svg" src="/logo-icon.svg"
alt="PragmaStack Logo" alt="Syndarix Logo"
width={24} width={24}
height={24} height={24}
className="h-6 w-6" className="h-6 w-6"
/> />
<h1 className="text-base font-semibold">PragmaStack</h1> <h1 className="text-base font-semibold">Syndarix</h1>
<Badge variant="secondary" className="text-xs"> <Badge variant="secondary" className="text-xs">
Dev Dev
</Badge> </Badge>

View File

@@ -14,8 +14,8 @@ import { Link } from '@/lib/i18n/routing';
const commands = [ const commands = [
{ text: '# Clone the repository', delay: 0 }, { text: '# Clone the repository', delay: 0 },
{ text: '$ git clone https://github.com/your-org/fast-next-template.git', delay: 800 }, { text: '$ git clone https://gitea.pragmazest.com/cardosofelipe/syndarix.git', delay: 800 },
{ text: '$ cd fast-next-template', delay: 1600 }, { text: '$ cd syndarix', delay: 1600 },
{ text: '', delay: 2200 }, { text: '', delay: 2200 },
{ text: '# Start with Docker (one command)', delay: 2400 }, { text: '# Start with Docker (one command)', delay: 2400 },
{ text: '$ docker-compose up', delay: 3200 }, { text: '$ docker-compose up', delay: 3200 },

View File

@@ -49,7 +49,7 @@ export function CTASection({ onOpenDemoModal }: CTASectionProps) {
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 pt-4"> <div className="flex flex-col sm:flex-row items-center justify-center gap-4 pt-4">
<Button asChild size="lg" className="gap-2 text-base group"> <Button asChild size="lg" className="gap-2 text-base group">
<a <a
href="https://github.com/your-org/fast-next-template" href="https://gitea.pragmazest.com/cardosofelipe/syndarix"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@@ -75,7 +75,7 @@ export function CTASection({ onOpenDemoModal }: CTASectionProps) {
</Button> </Button>
<Button asChild size="lg" variant="ghost" className="gap-2 text-base group"> <Button asChild size="lg" variant="ghost" className="gap-2 text-base group">
<a <a
href="https://github.com/your-org/fast-next-template#documentation" href="https://gitea.pragmazest.com/cardosofelipe/syndarix#documentation"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >

View File

@@ -44,7 +44,7 @@ const features = [
'12+ documentation guides covering architecture, design system, testing patterns, deployment, and AI code generation guidelines. Interactive API docs with Swagger and ReDoc', '12+ documentation guides covering architecture, design system, testing patterns, deployment, and AI code generation guidelines. Interactive API docs with Swagger and ReDoc',
highlight: 'Developer-first docs', highlight: 'Developer-first docs',
ctaText: 'Browse Docs', ctaText: 'Browse Docs',
ctaHref: 'https://github.com/your-org/fast-next-template#documentation', ctaHref: 'https://gitea.pragmazest.com/cardosofelipe/syndarix#documentation',
}, },
{ {
icon: Server, icon: Server,
@@ -53,7 +53,7 @@ const features = [
'Docker deployment configs, database migrations with Alembic helpers, connection pooling, health checks, monitoring setup, and production security headers', 'Docker deployment configs, database migrations with Alembic helpers, connection pooling, health checks, monitoring setup, and production security headers',
highlight: 'Deploy with confidence', highlight: 'Deploy with confidence',
ctaText: 'Deployment Guide', ctaText: 'Deployment Guide',
ctaHref: 'https://github.com/your-org/fast-next-template#deployment', ctaHref: 'https://gitea.pragmazest.com/cardosofelipe/syndarix#deployment',
}, },
{ {
icon: Code, icon: Code,

View File

@@ -48,13 +48,13 @@ export function Header({ onOpenDemoModal }: HeaderProps) {
> >
<Image <Image
src="/logo-icon.svg" src="/logo-icon.svg"
alt="PragmaStack Logo" alt="Syndarix Logo"
width={32} width={32}
height={32} height={32}
className="h-8 w-8" className="h-8 w-8"
/> />
<span className="bg-gradient-to-r from-primary to-primary/60 bg-clip-text text-transparent"> <span className="bg-gradient-to-r from-primary to-primary/60 bg-clip-text text-transparent">
PragmaStack Syndarix
</span> </span>
</Link> </Link>
@@ -72,7 +72,7 @@ export function Header({ onOpenDemoModal }: HeaderProps) {
{/* GitHub Link with Star */} {/* GitHub Link with Star */}
<a <a
href="https://github.com/your-org/fast-next-template" href="https://gitea.pragmazest.com/cardosofelipe/syndarix"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors" className="flex items-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
@@ -135,7 +135,7 @@ export function Header({ onOpenDemoModal }: HeaderProps) {
{/* GitHub Link */} {/* GitHub Link */}
<a <a
href="https://github.com/your-org/fast-next-template" href="https://gitea.pragmazest.com/cardosofelipe/syndarix"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
onClick={() => setMobileMenuOpen(false)} onClick={() => setMobileMenuOpen(false)}

View File

@@ -72,7 +72,7 @@ export function HeroSection({ onOpenDemoModal }: HeroSectionProps) {
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.2 }} transition={{ duration: 0.5, delay: 0.2 }}
> >
Opinionated, secure, and production-ready. PragmaStack gives you the solid foundation Opinionated, secure, and production-ready. Syndarix gives you the solid foundation
you need to stop configuring and start shipping.{' '} you need to stop configuring and start shipping.{' '}
<span className="text-foreground font-medium">Start building features on day one.</span> <span className="text-foreground font-medium">Start building features on day one.</span>
</motion.p> </motion.p>
@@ -93,7 +93,7 @@ export function HeroSection({ onOpenDemoModal }: HeroSectionProps) {
</Button> </Button>
<Button asChild size="lg" variant="outline" className="gap-2 text-base group"> <Button asChild size="lg" variant="outline" className="gap-2 text-base group">
<a <a
href="https://github.com/your-org/fast-next-template" href="https://gitea.pragmazest.com/cardosofelipe/syndarix"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >

View File

@@ -33,7 +33,7 @@ export function PhilosophySection() {
viewport={{ once: true, margin: '-100px' }} viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.6 }} transition={{ duration: 0.6 }}
> >
<h2 className="text-3xl md:text-4xl font-bold mb-6">Why PragmaStack?</h2> <h2 className="text-3xl md:text-4xl font-bold mb-6">Why Syndarix?</h2>
<div className="space-y-4 text-lg text-muted-foreground leading-relaxed"> <div className="space-y-4 text-lg text-muted-foreground leading-relaxed">
<p> <p>
We built this template after rebuilding the same authentication, authorization, and We built this template after rebuilding the same authentication, authorization, and

View File

@@ -13,8 +13,8 @@ import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
const codeString = `# Clone and start with Docker const codeString = `# Clone and start with Docker
git clone https://github.com/your-org/fast-next-template.git git clone https://gitea.pragmazest.com/cardosofelipe/syndarix.git
cd fast-next-template cd syndarix
docker-compose up docker-compose up
# Or set up locally # Or set up locally

View File

@@ -18,12 +18,12 @@ export function Footer() {
<div className="flex items-center gap-2 text-center text-sm text-muted-foreground md:text-left"> <div className="flex items-center gap-2 text-center text-sm text-muted-foreground md:text-left">
<Image <Image
src="/logo-icon.svg" src="/logo-icon.svg"
alt="PragmaStack Logo" alt="Syndarix Logo"
width={20} width={20}
height={20} height={20}
className="h-5 w-5 opacity-70" className="h-5 w-5 opacity-70"
/> />
<span>© {currentYear} PragmaStack. All rights reserved.</span> <span>© {currentYear} Syndarix. All rights reserved.</span>
</div> </div>
<div className="flex space-x-6"> <div className="flex space-x-6">
<Link <Link
@@ -33,7 +33,7 @@ export function Footer() {
Settings Settings
</Link> </Link>
<a <a
href="https://github.com/cardosofelipe/pragmastack" href="https://gitea.pragmazest.com/cardosofelipe/syndarix"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-sm text-muted-foreground hover:text-foreground transition-colors" className="text-sm text-muted-foreground hover:text-foreground transition-colors"

View File

@@ -86,12 +86,12 @@ export function Header() {
<Link href="/" className="flex items-center space-x-2"> <Link href="/" className="flex items-center space-x-2">
<Image <Image
src="/logo-icon.svg" src="/logo-icon.svg"
alt="PragmaStack Logo" alt="Syndarix Logo"
width={32} width={32}
height={32} height={32}
className="h-8 w-8" className="h-8 w-8"
/> />
<span className="text-xl font-bold text-foreground">PragmaStack</span> <span className="text-xl font-bold text-foreground">Syndarix</span>
</Link> </Link>
{/* Navigation Links */} {/* Navigation Links */}

View File

@@ -13,8 +13,8 @@ export type Locale = 'en' | 'it';
*/ */
export const siteConfig = { export const siteConfig = {
name: { name: {
en: 'PragmaStack', en: 'Syndarix',
it: 'PragmaStack', it: 'Syndarix',
}, },
description: { description: {
en: 'Production-ready FastAPI + Next.js full-stack template with authentication, admin panel, and comprehensive testing', en: 'Production-ready FastAPI + Next.js full-stack template with authentication, admin panel, and comprehensive testing',

View File

@@ -1,6 +1,6 @@
/** /**
* Tests for Home Page * Tests for Home Page
* Tests for the new PragmaStack landing page * Tests for the new Syndarix landing page
*/ */
import { render, screen, within, fireEvent } from '@testing-library/react'; import { render, screen, within, fireEvent } from '@testing-library/react';
@@ -87,13 +87,13 @@ describe('HomePage', () => {
it('renders header with logo', () => { it('renders header with logo', () => {
render(<Home />); render(<Home />);
const header = screen.getByRole('banner'); const header = screen.getByRole('banner');
expect(within(header).getByText('PragmaStack')).toBeInTheDocument(); expect(within(header).getByText('Syndarix')).toBeInTheDocument();
}); });
it('renders footer with copyright', () => { it('renders footer with copyright', () => {
render(<Home />); render(<Home />);
const footer = screen.getByRole('contentinfo'); const footer = screen.getByRole('contentinfo');
expect(within(footer).getByText(/PragmaStack. MIT Licensed/i)).toBeInTheDocument(); expect(within(footer).getByText(/Syndarix. MIT Licensed/i)).toBeInTheDocument();
}); });
}); });
@@ -210,7 +210,7 @@ describe('HomePage', () => {
describe('Philosophy Section', () => { describe('Philosophy Section', () => {
it('renders why this template exists', () => { it('renders why this template exists', () => {
render(<Home />); render(<Home />);
expect(screen.getByText(/Why PragmaStack\?/i)).toBeInTheDocument(); expect(screen.getByText(/Why Syndarix\?/i)).toBeInTheDocument();
}); });
it('renders what you wont find section', () => { it('renders what you wont find section', () => {

View File

@@ -71,7 +71,7 @@ describe('CTASection', () => {
); );
const githubLink = screen.getByRole('link', { name: /get started on github/i }); const githubLink = screen.getByRole('link', { name: /get started on github/i });
expect(githubLink).toHaveAttribute('href', 'https://github.com/your-org/fast-next-template'); expect(githubLink).toHaveAttribute('href', 'https://gitea.pragmazest.com/cardosofelipe/syndarix');
expect(githubLink).toHaveAttribute('target', '_blank'); expect(githubLink).toHaveAttribute('target', '_blank');
expect(githubLink).toHaveAttribute('rel', 'noopener noreferrer'); expect(githubLink).toHaveAttribute('rel', 'noopener noreferrer');
}); });
@@ -101,7 +101,7 @@ describe('CTASection', () => {
const docsLink = screen.getByRole('link', { name: /read documentation/i }); const docsLink = screen.getByRole('link', { name: /read documentation/i });
expect(docsLink).toHaveAttribute( expect(docsLink).toHaveAttribute(
'href', 'href',
'https://github.com/your-org/fast-next-template#documentation' 'https://gitea.pragmazest.com/cardosofelipe/syndarix#documentation'
); );
expect(docsLink).toHaveAttribute('target', '_blank'); expect(docsLink).toHaveAttribute('target', '_blank');
expect(docsLink).toHaveAttribute('rel', 'noopener noreferrer'); expect(docsLink).toHaveAttribute('rel', 'noopener noreferrer');

View File

@@ -55,7 +55,7 @@ describe('Header', () => {
/> />
); );
expect(screen.getByText('PragmaStack')).toBeInTheDocument(); expect(screen.getByText('Syndarix')).toBeInTheDocument();
}); });
it('logo links to homepage', () => { it('logo links to homepage', () => {
@@ -67,7 +67,7 @@ describe('Header', () => {
/> />
); );
const logoLink = screen.getByRole('link', { name: /PragmaStack/i }); const logoLink = screen.getByRole('link', { name: /Syndarix/i });
expect(logoLink).toHaveAttribute('href', '/'); expect(logoLink).toHaveAttribute('href', '/');
}); });
@@ -97,12 +97,12 @@ describe('Header', () => {
const githubLinks = screen.getAllByRole('link', { name: /github/i }); const githubLinks = screen.getAllByRole('link', { name: /github/i });
const desktopGithubLink = githubLinks.find((link) => const desktopGithubLink = githubLinks.find((link) =>
link.getAttribute('href')?.includes('github.com') link.getAttribute('href')?.includes('gitea.pragmazest.com')
); );
expect(desktopGithubLink).toHaveAttribute( expect(desktopGithubLink).toHaveAttribute(
'href', 'href',
'https://github.com/your-org/fast-next-template' 'https://gitea.pragmazest.com/cardosofelipe/syndarix'
); );
expect(desktopGithubLink).toHaveAttribute('target', '_blank'); expect(desktopGithubLink).toHaveAttribute('target', '_blank');
expect(desktopGithubLink).toHaveAttribute('rel', 'noopener noreferrer'); expect(desktopGithubLink).toHaveAttribute('rel', 'noopener noreferrer');

View File

@@ -100,7 +100,7 @@ describe('HeroSection', () => {
); );
const githubLink = screen.getByRole('link', { name: /view on github/i }); const githubLink = screen.getByRole('link', { name: /view on github/i });
expect(githubLink).toHaveAttribute('href', 'https://github.com/your-org/fast-next-template'); expect(githubLink).toHaveAttribute('href', 'https://gitea.pragmazest.com/cardosofelipe/syndarix');
expect(githubLink).toHaveAttribute('target', '_blank'); expect(githubLink).toHaveAttribute('target', '_blank');
expect(githubLink).toHaveAttribute('rel', 'noopener noreferrer'); expect(githubLink).toHaveAttribute('rel', 'noopener noreferrer');
}); });

View File

@@ -20,7 +20,7 @@ describe('Footer', () => {
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
expect( expect(
screen.getByText(`© ${currentYear} PragmaStack. All rights reserved.`) screen.getByText(`© ${currentYear} Syndarix. All rights reserved.`)
).toBeInTheDocument(); ).toBeInTheDocument();
}); });

View File

@@ -63,7 +63,7 @@ describe('Header', () => {
render(<Header />); render(<Header />);
expect(screen.getByText('PragmaStack')).toBeInTheDocument(); expect(screen.getByText('Syndarix')).toBeInTheDocument();
}); });
it('renders theme toggle', () => { it('renders theme toggle', () => {

View File

@@ -27,8 +27,8 @@ describe('metadata utilities', () => {
}); });
it('should have English and Italian names', () => { it('should have English and Italian names', () => {
expect(siteConfig.name.en).toBe('PragmaStack'); expect(siteConfig.name.en).toBe('Syndarix');
expect(siteConfig.name.it).toBe('PragmaStack'); expect(siteConfig.name.it).toBe('Syndarix');
}); });
it('should have English and Italian descriptions', () => { it('should have English and Italian descriptions', () => {