Skip to content

Commit 1477023

Browse files
author
Dan Costello
committed
Initial commit
0 parents  commit 1477023

275 files changed

Lines changed: 22009 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslintrc.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": ["next/core-web-vitals", "next/typescript"]
3+
}

.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# deps
2+
/node_modules
3+
4+
# generated content
5+
.contentlayer
6+
.content-collections
7+
.source
8+
9+
# test & build
10+
/coverage
11+
/.next/
12+
/out/
13+
/build
14+
*.tsbuildinfo
15+
16+
# misc
17+
.DS_Store
18+
*.pem
19+
/.pnp
20+
.pnp.js
21+
22+
# others
23+
.env*.local
24+
.vercel
25+
next-env.d.ts

README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# UserClouds Documentation
2+
3+
This repository contains the official documentation for UserClouds products and services.
4+
5+
## Quick Start
6+
7+
### Prerequisites
8+
9+
- Node.js 18 or later
10+
- pnpm package manager
11+
12+
### Installation
13+
14+
```bash
15+
pnpm install
16+
```
17+
18+
### Development
19+
20+
Start the development server:
21+
22+
```bash
23+
pnpm dev
24+
```
25+
26+
Open [http://localhost:3000](http://localhost:3000) in your browser.
27+
28+
## Building
29+
30+
### Local Production Build
31+
32+
```bash
33+
pnpm build
34+
pnpm serve
35+
```
36+
37+
## Project Structure
38+
39+
```
40+
docs/
41+
├── app/ # Next.js application files
42+
├── content/ # Documentation content (Markdown/MDX)
43+
├── public/ # Static assets
44+
├── lib/ # Utility libraries
45+
├── scripts/ # Build scripts
46+
└── .source/ # Generated source files
47+
```
48+
49+
## Available Scripts
50+
51+
- `pnpm dev` - Start development server
52+
- `pnpm build` - Build production version
53+
- `pnpm serve` - Serve production build locally
54+
- `pnpm build:api` - Build OpenAPI documentation pages
55+
- `pnpm types:check` - Check TypeScript types
56+
- `pnpm clean` - Clean build artifacts
57+
58+
## License
59+
60+
Copyright © UserClouds, Inc. All rights reserved.

app/(home)/layout.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { ReactNode } from 'react';
2+
import { HomeLayout } from 'fumadocs-ui/layouts/home';
3+
import { baseOptions } from '@/app/layout.config';
4+
5+
export default function Layout({ children }: { children: ReactNode }) {
6+
return <HomeLayout
7+
{...baseOptions}
8+
links= {[
9+
{
10+
text: 'Documentation',
11+
url: '/docs',
12+
}
13+
]}
14+
>{children}</HomeLayout>
15+
}

app/(home)/page.tsx

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import Link from 'next/link';
2+
import Image from 'next/image';
3+
4+
export default function HomePage() {
5+
return (
6+
<main className="flex flex-1 flex-col items-center justify-center py-20 text-center px-4">
7+
<div className="mb-10 flex flex-col items-center gap-y-6">
8+
<Image
9+
src="/logo.png"
10+
alt="UserClouds Logo"
11+
width={240}
12+
height={33}
13+
className="dark:saturate-200"
14+
priority
15+
/>
16+
17+
<p className="text-xl text-fd-muted-foreground max-w-3xl">
18+
An open-source identity management platform that simplifies authentication, authorization,
19+
and user data handling for modern applications.
20+
</p>
21+
</div>
22+
23+
<div className="flex flex-col sm:flex-row gap-4">
24+
<Link
25+
href="/docs"
26+
className="inline-flex items-center justify-center rounded-lg bg-fd-primary px-6 py-3 text-sm font-medium text-white hover:bg-fd-primary/90 transition-colors"
27+
>
28+
Read Documentation
29+
</Link>
30+
<Link
31+
href="https://github.com/userclouds/userclouds-oss"
32+
className="inline-flex items-center justify-center rounded-lg border border-fd-border bg-background px-6 py-3 text-sm font-medium hover:bg-fd-muted/50 transition-colors"
33+
target="_blank"
34+
rel="noopener noreferrer"
35+
>
36+
<svg
37+
xmlns="http://www.w3.org/2000/svg"
38+
width="18"
39+
height="18"
40+
viewBox="0 0 24 24"
41+
fill="none"
42+
stroke="currentColor"
43+
strokeWidth="2"
44+
strokeLinecap="round"
45+
strokeLinejoin="round"
46+
className="mr-2"
47+
>
48+
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
49+
<path d="M9 18c-4.51 2-5-2-7-2" />
50+
</svg>
51+
GitHub
52+
</Link>
53+
</div>
54+
55+
<div className="mt-16 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8 max-w-6xl">
56+
<div className="flex flex-col items-center p-6 bg-fd-card rounded-lg border border-fd-border">
57+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="mb-4 text-fd-primary">
58+
<rect width="18" height="11" x="3" y="11" rx="2" ry="2" />
59+
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
60+
</svg>
61+
<h3 className="text-lg font-semibold mb-2">Authentication</h3>
62+
<p className="text-fd-muted-foreground text-center">Secure, flexible user authentication system with multiple identity providers</p>
63+
</div>
64+
65+
<div className="flex flex-col items-center p-6 bg-fd-card rounded-lg border border-fd-border">
66+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="mb-4 text-fd-primary">
67+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10" />
68+
</svg>
69+
<h3 className="text-lg font-semibold mb-2">Authorization</h3>
70+
<p className="text-fd-muted-foreground text-center">Fine-grained access control with easy-to-implement permission models</p>
71+
</div>
72+
73+
<div className="flex flex-col items-center p-6 bg-fd-card rounded-lg border border-fd-border">
74+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="mb-4 text-fd-primary">
75+
<path d="M21 5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2" />
76+
<path d="M16 2v4" />
77+
<path d="M8 2v4" />
78+
<path d="M2 10h20" />
79+
</svg>
80+
<h3 className="text-lg font-semibold mb-2">User Data Management</h3>
81+
<p className="text-fd-muted-foreground text-center">Centralized user data storage with privacy-preserving tokenization</p>
82+
</div>
83+
</div>
84+
</main>
85+
);
86+
}

app/api/search/route.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const dynamic = 'force-static';
2+
3+
import { source } from '@/lib/source';
4+
import { createFromSource } from 'fumadocs-core/search/server';
5+
6+
export const { staticGET: GET } = createFromSource(source);

app/components/Glossary.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use client';
2+
3+
import React from 'react';
4+
import Popover from './Popover';
5+
import { glossaryDefinitions } from '@/content/glossary-definitions';
6+
7+
type GlossaryProps = {
8+
children: string;
9+
term?: string
10+
};
11+
12+
const Glossary = ({ children, term }: GlossaryProps) => {
13+
term = term || children.trim();
14+
const definition = glossaryDefinitions[term];
15+
const definitionString = definition || 'No definition found.';
16+
return (
17+
<Popover content={definitionString}>
18+
<span style={{ textDecoration: 'underline dotted', cursor: 'help' }} className={definition ? '' : 'border-2 border-red-500'}>{term}</span>
19+
</Popover>
20+
);
21+
};
22+
23+
export default Glossary;

app/components/Popover.tsx

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
'use client';
2+
3+
import React, { useState, useRef, useEffect } from 'react';
4+
import { createPortal } from 'react-dom';
5+
6+
const Popover = ({ children, content }: { children: React.ReactNode, content: React.ReactNode }) => {
7+
const [isVisible, setIsVisible] = useState(false);
8+
const [popoverStyle, setPopoverStyle] = useState<React.CSSProperties>({});
9+
const popoverRef = useRef<HTMLDivElement>(null);
10+
const triggerRef = useRef<HTMLButtonElement>(null);
11+
12+
const toggleVisibility = () => {
13+
setIsVisible(v => {
14+
const next = !v;
15+
if (next && triggerRef.current) {
16+
const rect = triggerRef.current.getBoundingClientRect();
17+
setPopoverStyle({
18+
position: 'absolute',
19+
top: rect.bottom + window.scrollY + 4, // 4px gap
20+
left: rect.left + window.scrollX,
21+
zIndex: 1000
22+
});
23+
}
24+
return next;
25+
});
26+
};
27+
28+
// Focus management and Escape key
29+
useEffect(() => {
30+
if (isVisible && popoverRef.current) {
31+
popoverRef.current.focus();
32+
}
33+
}, [isVisible]);
34+
35+
useEffect(() => {
36+
if (!isVisible && triggerRef.current) {
37+
triggerRef.current.focus();
38+
}
39+
}, [isVisible]);
40+
41+
useEffect(() => {
42+
if (!isVisible) return;
43+
const handleKeyDown = (event: KeyboardEvent) => {
44+
if (event.key === 'Escape') {
45+
setIsVisible(false);
46+
}
47+
};
48+
document.addEventListener('keydown', handleKeyDown);
49+
return () => document.removeEventListener('keydown', handleKeyDown);
50+
}, [isVisible]);
51+
52+
useEffect(() => {
53+
const handleClickOutside = (event: MouseEvent) => {
54+
if (
55+
popoverRef.current &&
56+
!popoverRef.current.contains(event.target as Node) &&
57+
triggerRef.current &&
58+
!triggerRef.current.contains(event.target as Node)
59+
) {
60+
setIsVisible(false);
61+
}
62+
};
63+
document.addEventListener('mousedown', handleClickOutside);
64+
return () => document.removeEventListener('mousedown', handleClickOutside);
65+
}, []);
66+
67+
return (
68+
<span className="relative inline-block">
69+
<button
70+
ref={triggerRef}
71+
onClick={toggleVisibility}
72+
className="p-0 underline decoration-dotted text-inherit border-none background-none z-10"
73+
aria-haspopup="true"
74+
aria-expanded={isVisible}
75+
aria-controls="popover-content"
76+
type="button"
77+
>
78+
{children}
79+
</button>
80+
{isVisible && typeof window !== 'undefined' && createPortal(
81+
<div
82+
id="popover-content"
83+
ref={popoverRef}
84+
className={`absolute top-0 left-1/2 transform -translate-x-1/2 bg-fd-popover text-fd-popover-foreground border border-fd-border shadow-lg rounded-md p-4 z-10 whitespace-normal min-w-[250px] max-w-[320px] text-[13px]`}
85+
role="dialog"
86+
aria-modal="true"
87+
aria-label="Popover dialog"
88+
tabIndex={-1}
89+
style={popoverStyle}
90+
dangerouslySetInnerHTML={{ __html: content as string }} // Use dangerouslySetInnerHTML to set HTML content
91+
/>,
92+
document.body
93+
)}
94+
</span>
95+
);
96+
};
97+
98+
export default Popover;

app/docs/[[...slug]]/page.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { source } from '@/lib/source';
2+
import {
3+
DocsPage,
4+
DocsBody,
5+
DocsDescription,
6+
DocsTitle,
7+
} from 'fumadocs-ui/page';
8+
import { notFound } from 'next/navigation';
9+
import { createRelativeLink } from 'fumadocs-ui/mdx';
10+
import { getMDXComponents } from '@/mdx-components';
11+
12+
export default async function Page(props: {
13+
params: Promise<{ slug?: string[] }>;
14+
}) {
15+
const params = await props.params;
16+
const page = source.getPage(params.slug);
17+
if (!page) notFound();
18+
19+
const MDXContent = page.data.body;
20+
21+
return (
22+
<DocsPage
23+
toc={page.data.toc}
24+
full={page.data.full}
25+
tableOfContent={{
26+
style: 'clerk',
27+
}}>
28+
<DocsTitle className="font-normal border-b border-fd-muted-foreground pb-2">{page.data.title}</DocsTitle>
29+
<DocsDescription>{page.data.description}</DocsDescription>
30+
<DocsBody className="prose dark:prose-invert">
31+
<MDXContent
32+
components={getMDXComponents({
33+
a: createRelativeLink(source, page),
34+
})}
35+
/>
36+
</DocsBody>
37+
</DocsPage>
38+
);
39+
}
40+
41+
export async function generateStaticParams() {
42+
return source.generateParams();
43+
}
44+
45+
export async function generateMetadata(props: {
46+
params: Promise<{ slug?: string[] }>;
47+
}) {
48+
const params = await props.params;
49+
const page = source.getPage(params.slug);
50+
if (!page) notFound();
51+
52+
return {
53+
title: page.data.title,
54+
description: page.data.description,
55+
};
56+
}

0 commit comments

Comments
 (0)