Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2eb7d42
refactor(website): baseline setup
tornado-softwares Aug 16, 2026
9f090ba
refactor(website): update design system integration and theme variables
tornado-softwares Aug 16, 2026
93e9ae8
fix(website): link variants and style adjustments
tornado-softwares Aug 16, 2026
6d13149
style(website): navbar styling and theme fixes
tornado-softwares Aug 16, 2026
51a981a
style(website): tweak layout and component styles
tornado-softwares Aug 16, 2026
6927207
style(website): add light rays effect and improve hero section
tornado-softwares Aug 17, 2026
ac1dd4d
refactor(website): clean up landing page sections
tornado-softwares Aug 17, 2026
071b644
refactor(website): rework landing page sections and components
tornado-softwares Aug 18, 2026
4c98afc
refactor(website): add main landing switcher
tornado-softwares Aug 18, 2026
438c33a
refactor(website): remake faq component
tornado-softwares Aug 20, 2026
c8d0089
fix(website): structured data and layout tweaks
tornado-softwares Aug 20, 2026
7a37e16
refactor(website): migrate features section to bento grid
tornado-softwares Aug 23, 2026
6e92ca0
refactor(website): complete features bento grid and visual editor sec…
tornado-softwares Aug 23, 2026
059f562
style(website): improve documentation table and add footer status badge
tornado-softwares Aug 25, 2026
85fd9ac
refactor(website): align app folder structure
tornado-softwares Aug 26, 2026
bc2a661
feat(website): add static pages plugin with optional prerendering con…
aymericzip Aug 26, 2026
1ef0274
style(website): fix background color class in IDESection component
aymericzip Aug 26, 2026
68f653d
Update comment text color in BlogCommentSection
tornado-softwares Aug 26, 2026
b9221dd
fix(website): conditionally execute compression and inlining based on…
aymericzip Aug 26, 2026
d4ab761
refactor(website): renamed components and some classes according to the
tornado-softwares Aug 26, 2026
4fef8fb
feat(website): add DISABLE_OPTIMIZATION argument and environment vari…
aymericzip Aug 26, 2026
494b2a4
refactor(website): fix review point redesign pr
aymericzip Aug 26, 2026
edf0820
style(design-system): fixed the shiki higlight styling
tornado-softwares Aug 27, 2026
b2e4460
style(design-system): add browser top bar border and fix navbar
tornado-softwares Aug 27, 2026
22dcda4
refactor(website): fixed doc aside, inputs, and small details
tornado-softwares Aug 28, 2026
8bee1d1
style(design-system): fixed betterstack widget theme
tornado-softwares Aug 28, 2026
be013c5
refactor(website): rebuilt the faq section with design system accordion
tornado-softwares Aug 28, 2026
972b177
fix(website): missing structured data
tornado-softwares Aug 28, 2026
ef5b37d
refactor(website): adjust redesign
aymericzip Aug 29, 2026
37e4a84
refactor(design-system): reverted css and fixed styles for website and
tornado-softwares Sep 1, 2026
9010c9a
fix(app): fix hydration errors
aymericzip Sep 1, 2026
a70d0c6
style(design-system): update light card color
aymericzip Sep 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .agents/skills/intlayer-content/references/concept_content.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,15 +344,15 @@ Transforms the dictionary into a per-locale dictionary where each field declared

**Example:**

```json
```jsonc
// Per-locale dictionary
{
"key": "about-page",
"locale": "en",
"content": {
"title": "About Us", // This becomes a translation node for 'en'
"description": "Learn more about our company"
}
"description": "Learn more about our company",
},
}
```

Expand Down Expand Up @@ -422,7 +422,7 @@ Instructions for automatically filling dictionary content from external sources.

**Examples:**

```json
```jsonc
// Disable filling
{
"fill": false
Expand Down
2 changes: 1 addition & 1 deletion apps/app/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@ VITE_STRIPE_ONE_TIME_PAYMENT_PRICE_ID=
# Election #
#######################################################################################

GH_TOKEN=
GH_TOKEN=
1 change: 1 addition & 0 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"@tanstack/react-table": "9.2.4",
"@tanstack/react-virtual": "3.14.10",
"@tanstack/router-plugin": "1.168.35",
"@tanstack/start-static-server-functions": "^1.167.32",
"defu": "6.1.7",
"framer-motion": "13.1.1",
"fuse.js": "7.5.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { Button } from '@intlayer/design-system/button';
import { Container } from '@intlayer/design-system/container';
import { DropDown } from '@intlayer/design-system/drop-down';
import { useIsMounted } from '@intlayer/design-system/hooks';
import { Modal } from '@intlayer/design-system/modal';
import { ChevronsUpDown } from 'lucide-react';
import { type FC, useState } from 'react';
Expand All @@ -16,6 +17,7 @@ import { OrganizationCreationForm } from '../OrganizationForm/OrganizationCreati

export const OrganizationDropdown: FC = () => {
const { session } = useSession();
const isMounted = useIsMounted();
const { data: organizations } = useGetOrganizations();
const [isCreationModalOpen, setIsCreationModalOpen] = useState(false);
const { mutate: selectOrganization, isPending: isSelectOrganizationLoading } =
Expand Down Expand Up @@ -43,12 +45,21 @@ export const OrganizationDropdown: FC = () => {
selectOrganization(organizationId);
};

const otherOrganizations = (organizations?.data ?? [])
.filter(
(organizationEl: any) =>
String(organizationEl.id) !== String(organization?.id)
)
.slice(0, 10);
// The organizations list is client-only: the API client authenticates with
// `credentials: 'include'`, which is a browser mechanism, so SSR always sees
// an empty list and renders the empty state. Holding the list back until
// after mount keeps the first client render identical to the server markup —
// otherwise the fetch resolving around hydration swaps the empty state for
// buttons mid-hydration and the tree is thrown away. The panel is closed at
// that point, so nothing is visibly deferred.
const otherOrganizations = isMounted
? (organizations?.data ?? [])
.filter(
(organizationEl: any) =>
String(organizationEl.id) !== String(organization?.id)
)
.slice(0, 10)
: [];

return organization ? (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { Button } from '@intlayer/design-system/button';
import { Container } from '@intlayer/design-system/container';
import { DropDown } from '@intlayer/design-system/drop-down';
import { useIsMounted } from '@intlayer/design-system/hooks';
import { Modal } from '@intlayer/design-system/modal';
import { ChevronsUpDown } from 'lucide-react';
import { type ComponentProps, type FC, useState } from 'react';
Expand All @@ -19,6 +20,7 @@ type ProjectDropdownProps = Partial<ComponentProps<typeof DropDown.Panel>> & {

export const ProjectDropdown: FC<ProjectDropdownProps> = (props) => {
const { session } = useSession();
const isMounted = useIsMounted();

const { data: projects } = useGetProjects();
const { mutate: selectProject, isPending: isSelectProjectLoading } =
Expand All @@ -43,9 +45,15 @@ export const ProjectDropdown: FC<ProjectDropdownProps> = (props) => {
selectProject(projectId);
};

const otherProjects = (projects?.data ?? [])
.filter((projectEl: any) => String(projectEl.id) !== String(project?.id))
.slice(0, 10);
// Client-only list held back until after mount — see `OrganizationDropdown`
// for why SSR can never populate it.
const otherProjects = isMounted
? (projects?.data ?? [])
.filter(
(projectEl: any) => String(projectEl.id) !== String(project?.id)
)
.slice(0, 10)
: [];

return project ? (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -628,11 +628,9 @@ export const DashboardSidebar: FC<DashboardSidebarProps> = ({
/>
</nav>

{!isCollapsed &&
!IS_SELF_HOSTED &&
process.env.NODE_ENV === 'development' && (
<ReviewerMarketplaceBanner />
)}
{!isCollapsed && !IS_SELF_HOSTED && import.meta.env.DEV && (
<ReviewerMarketplaceBanner />
)}

{/* Environment switcher — shown when project has >1 environments */}
{environments.length > 1 &&
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import {} from '@intlayer/design-system/api';
import { Button } from '@intlayer/design-system/button';
import { Container } from '@intlayer/design-system/container';
import { usePersistedStore } from '@intlayer/design-system/hooks';
import { useIsMounted, usePersistedStore } from '@intlayer/design-system/hooks';
import { App_ReviewerMarketplace_Path } from '@intlayer/design-system/routes';
import { X } from 'lucide-react';
import { type FC, useEffect } from 'react';
import type { FC } from 'react';
import { useIntlayer } from 'react-intlayer';
import { Link } from '#components/Link/Link';

const STORAGE_KEY = 'isReviewerMarketplaceBannerClosed';

export const ReviewerMarketplaceBanner: FC = () => {
const { reviewerMarketplace } = useIntlayer('dashboard-sidebar');
const [isVisible, setIsVisible] = usePersistedStore(STORAGE_KEY, false);
// The key stores the dismissal, as its name says. It previously held
// `isVisible`, so closing the banner persisted `false`, which the mount
// effect then read as "not closed" and re-opened — the banner could never be
// dismissed for good.
const [isClosed, setIsClosed] = usePersistedStore(STORAGE_KEY, false);
const isMounted = useIsMounted();

useEffect(() => {
const isClosed = localStorage.getItem(STORAGE_KEY) === 'true';
if (!isClosed) {
setIsVisible(true);
}
}, []);

if (!isVisible) return <></>;
// Stays hidden until mount: the server cannot know about a dismissal, and
// rendering before `usePersistedStore` has read it back would flash a banner
// the user already closed.
if (!isMounted || isClosed) return <></>;

return (
<Container
Expand All @@ -39,7 +39,7 @@ export const ReviewerMarketplaceBanner: FC = () => {
variant="hoverable"
size="icon-sm"
Icon={X}
onClick={() => setIsVisible(false)}
onClick={() => setIsClosed(true)}
/>
</div>
<span className="text-neutral text-sm">
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/components/Dashboard/DashboardSkeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { FC } from 'react';
import { Skeleton } from '#components/Skeleton';

export const DashboardSkeleton: FC = () => (
<div className="dashboard-theme flex h-screen max-h-screen flex-col bg-card">
<div className="flex h-screen max-h-screen flex-col bg-card">
{/* Navbar Skeleton */}
<Container
className="sticky top-0 z-50 flex w-full flex-col gap-3 p-4"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const ContactChat: FC<ContactChatProps> = ({ reviewer }) => {
transparency="full"
className="flex flex-1 flex-col overflow-hidden"
>
<div className="border-neutral/20 border-b px-4 py-3">
<div className="border-neutral border-neutral/20 border-b px-4 py-3">
<p className="font-semibold text-sm">
{content.contact} {reviewer.name ?? 'reviewer'}
</p>
Expand Down
6 changes: 6 additions & 0 deletions apps/app/src/components/SwitchThemeSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useIsMounted } from '@intlayer/design-system/hooks';
import {
SwitchSelector,
type SwitchSelectorChoices,
Expand All @@ -9,6 +10,11 @@ import { useTheme } from '#/providers/ThemeProvider';

export const SwitchThemeSwitcher: FC = () => {
const { resolvedTheme, setTheme } = useTheme();
const isMounted = useIsMounted();

if (!isMounted) {
return null;
}

const themeSwitcher = [
{
Expand Down
15 changes: 13 additions & 2 deletions apps/app/src/hooks/useDashboardRightPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import { useSyncExternalStore } from 'react';

type PanelState = { activePanel: string | null };

/**
* Snapshot handed to the server render and to hydration. It has to be the same
* reference on every call, otherwise `useSyncExternalStore` sees a new value
* each render and loops.
*/
const SERVER_PANEL_STATE: PanelState = { activePanel: null };

class PanelObservable {
private listeners = new Set<() => void>();
private state: PanelState = { activePanel: null };
Expand All @@ -15,6 +22,8 @@ class PanelObservable {

getSnapshot = () => this.state;

getServerSnapshot = () => SERVER_PANEL_STATE;

open = (id: string) => {
const next = this.state.activePanel === id ? null : id;
if (this.state.activePanel === next) return;
Expand All @@ -29,7 +38,9 @@ class PanelObservable {
};

private emit = () => {
this.listeners.forEach((l) => l());
this.listeners.forEach((listener) => {
listener();
});
};
}

Expand All @@ -39,7 +50,7 @@ export const useDashboardRightPanel = () => {
const state = useSyncExternalStore(
dashboardRightPanelManager.subscribe,
dashboardRightPanelManager.getSnapshot,
() => ({ activePanel: null }) as PanelState
dashboardRightPanelManager.getServerSnapshot
);

return {
Expand Down
11 changes: 10 additions & 1 deletion apps/app/src/hooks/useVisualEditorKeys.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { useSyncExternalStore } from 'react';

/**
* Snapshot handed to the server render and to hydration. It has to be the same
* reference on every call, otherwise `useSyncExternalStore` sees a new value
* each render and loops.
*/
const SERVER_KEYS: string[] = [];

class DisplayedKeysObservable {
private listeners = new Set<() => void>();
private state: string[] = [];
Expand All @@ -13,6 +20,8 @@ class DisplayedKeysObservable {

getSnapshot = (): string[] => this.state;

getServerSnapshot = (): string[] => SERVER_KEYS;

setKeys = (keys: string[]) => {
if (
keys.length === this.state.length &&
Expand All @@ -32,5 +41,5 @@ export const useVisualEditorKeys = (): string[] =>
useSyncExternalStore(
visualEditorKeysManager.subscribe,
visualEditorKeysManager.getSnapshot,
() => []
visualEditorKeysManager.getServerSnapshot
);
17 changes: 17 additions & 0 deletions apps/app/src/router.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Loader } from '@intlayer/design-system/loader';
import { getQueryClient } from '@intlayer/design-system/providers';
import { createRouter as createTanStackRouter } from '@tanstack/react-router';
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query';
import { NotFoundComponent } from '#components/NotFoundComponent';
import { routeTree } from './routeTree.gen';

Expand Down Expand Up @@ -29,6 +30,22 @@ export function getRouter() {
defaultPendingComponent: Loader,
});

/**
* Dehydrates the query cache into the SSR payload and rehydrates it before
* the first client render. Without it, loaders such as the root's
* `sessionQueryOptions` prime `['session']` on the server only, so the server
* markup renders session-dependent UI (organization/project dropdowns) that
* the first client render — reading an empty cache — omits, and hydration
* fails.
*/
setupRouterSsrQueryIntegration({
router,
queryClient,
// `ReactQueryProvider` in `__root.tsx` already provides this same client,
// along with the toast and invalidation wiring the integration lacks.
wrapQueryClient: false,
});

return router;
}

Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/routes/{-$locale}/_dashboard/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ function DashboardLayout() {
return (
<AuthenticationBarrier accessRule="authenticated" locale={locale}>
<div
className="dashboard-theme flex h-screen max-h-screen flex-col bg-card md:overflow-hidden"
className="flex h-screen max-h-screen flex-col bg-card md:overflow-hidden"
style={{ fontSize: '75%' }}
>
<DashboardNavbar items={navigationItems} />
Expand Down
Loading
Loading