Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
},
"cssVars": {},
"categories": ["upload", "laravel", "inertia"],
"docs": "\n--------------------------------------------------------------------------\n Remaining Installation Steps:\n--------------------------------------------------------------------------\n\n1. Configure Routes\n\nAdd a route for the SignedUrlController to your routes file:\n\nRoute::post('/upload/signed-url',\n \\App\\Http\\Controllers\\Astrify\\SignedUrlController::class\n)->name('upload.signed-url'); // if the environment is public be sure to require auth\n\n2. Install Laravel Dependencies\n\nInstall the Flysystem S3 package via the Composer package manager:\n\ncomposer require league/flysystem-aws-s3-v3:^3.0\n\n3. Configure your S3 Storage\n\nFor local development we recommend using MinIO as a self-hosted S3-compatible storage solution. Set the required AWS environment variables in your .env file.\n"
"docs": "The Astrify Upload Module has been installed. For usage instructions, visit https://astrify.com/docs/upload"
},
{
"name": "paginated-table",
Expand All @@ -42,7 +42,7 @@
},
"cssVars": {},
"categories": ["table", "pagination", "inertia", "laravel"],
"docs": "For a usage example, visit https://astrify.com/docs/paginated-table"
"docs": "The Paginated Table component has been installed. For usage instructions, visit https://astrify.com/docs/paginated-table"
},
{
"name": "json-table",
Expand All @@ -62,7 +62,7 @@
},
"cssVars": {},
"categories": ["table", "pagination", "data-fetching", "inertia", "laravel"],
"docs": "For a usage example, visit https://astrify.com/docs/json-table"
"docs": "The JSON Table component has been installed. For usage instructions, visit https://astrify.com/docs/json-table"
}
]
}
2 changes: 1 addition & 1 deletion resources/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
--chart-5: var(--color-blue-800);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary: oklch(0.985 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
Expand Down
2 changes: 1 addition & 1 deletion resources/js/components/docs/code-collapsible-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function CodeCollapsibleWrapper({ className, children, ...props }: React.
<CollapsibleContent forceMount className="relative mt-6 overflow-hidden data-[state=closed]:max-h-64 [&>figure]:mt-0 [&>figure]:md:!mx-0">
{children}
</CollapsibleContent>
<CollapsibleTrigger className="absolute inset-x-0 -bottom-2 flex h-20 items-center justify-center rounded-b-lg bg-gradient-to-b from-code/70 to-code text-sm text-muted-foreground group-data-[state=open]/collapsible:hidden">
<CollapsibleTrigger className="absolute inset-x-0 -bottom-2 flex h-20 items-center justify-center rounded-b-lg bg-gradient-to-b from-code/70 to-code text-sm text-code-foreground hover:text-foreground group-data-[state=open]/collapsible:hidden">
{isOpened ? 'Collapse' : 'Expand'}
</CollapsibleTrigger>
</Collapsible>
Expand Down
4 changes: 2 additions & 2 deletions resources/js/components/docs/component-preview-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ export function ComponentPreviewTabs({
)}
</div>
</Tabs>
<div data-tab={tab} className="relative rounded-lg border data-[tab=code]:border-code md:-mx-1">
<div data-tab={tab} className="relative rounded-lg border border-border data-[tab=code]:border-code md:-mx-1">
<div data-slot="preview" data-active={tab === 'preview'} className="invisible data-[active=true]:visible">
<div
data-align={align}
className={cn(
'preview flex h-[450px] w-full justify-center p-10 data-[align=center]:items-center data-[align=end]:items-end data-[align=start]:items-start',
'preview flex h-[450px] w-full justify-center bg-background p-10 data-[align=center]:items-center data-[align=end]:items-end data-[align=start]:items-start',
)}
>
{component}
Expand Down
10 changes: 5 additions & 5 deletions resources/js/components/docs/mdx-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const mdxComponents = {
<h2
id={props.children?.toString().replace(/ /g, '-').replace(/'/g, '').replace(/\?/g, '').toLowerCase()}
className={cn(
'font-heading mt-12 scroll-m-28 text-2xl font-medium tracking-tight first:mt-0 lg:mt-20 [&+p]:!mt-4 *:[code]:text-2xl',
'font-heading mt-12 scroll-m-28 text-2xl font-medium tracking-tight first:mt-0 [&+p]:!mt-4 *:[code]:text-2xl',
className,
)}
{...props}
Expand Down Expand Up @@ -82,16 +82,16 @@ export const mdxComponents = {
ol: ({ className, ...props }: React.ComponentProps<'ol'>) => <ol className={cn('my-6 ml-6 list-decimal', className)} {...props} />,
li: ({ className, ...props }: React.ComponentProps<'li'>) => <li className={cn('mt-2', className)} {...props} />,
blockquote: ({ className, ...props }: React.ComponentProps<'blockquote'>) => (
<blockquote className={cn('mt-6 border-l-2 pl-6 italic', className)} {...props} />
<blockquote className={cn('mt-6 border-l-2 border-border pl-6 italic text-muted-foreground', className)} {...props} />
),
img: ({ className, alt, ...props }: React.ComponentProps<'img'>) => <img className={cn('rounded-md', className)} alt={alt} {...props} />,
hr: ({ ...props }: React.ComponentProps<'hr'>) => <hr className="my-4 md:my-8" {...props} />,
hr: ({ ...props }: React.ComponentProps<'hr'>) => <hr className="my-4 border-border md:my-8" {...props} />,
table: ({ className, ...props }: React.ComponentProps<'table'>) => (
<div className="my-6 w-full overflow-y-auto">
<table className={cn('relative w-full overflow-hidden border-none text-sm', className)} {...props} />
</div>
),
tr: ({ className, ...props }: React.ComponentProps<'tr'>) => <tr className={cn('last:border-b-none m-0 border-b', className)} {...props} />,
tr: ({ className, ...props }: React.ComponentProps<'tr'>) => <tr className={cn('last:border-b-none m-0 border-b border-border', className)} {...props} />,
th: ({ className, ...props }: React.ComponentProps<'th'>) => (
<th className={cn('px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right', className)} {...props} />
),
Expand Down Expand Up @@ -152,7 +152,7 @@ export const mdxComponents = {
if (typeof props.children === 'string') {
return (
<code
className={cn('relative rounded-md bg-muted px-[0.3rem] py-[0.2rem] font-mono text-[0.8rem] outline-none', className)}
className={cn('relative rounded-md bg-muted px-[0.3rem] py-[0.2rem] font-mono text-[0.8rem] text-foreground outline-none', className)}
{...props}
/>
);
Expand Down
6 changes: 3 additions & 3 deletions resources/js/components/landing/hero-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Clock, Package, Rocket, ShieldCheck, Wrench, Zap } from 'lucide-react';
export function HeroSection() {
return (
<section className="relative z-10">
<div className="mx-auto max-w-7xl px-6 py-20 lg:px-8">
<div className="mx-auto max-w-7xl px-6 py-12 lg:py-20 lg:px-8">
<Badge variant="secondary" className="inline-flex items-center gap-2">
<Rocket className="h-3.5 w-3.5" />
Free Full-Stack Modules — Available Now!
Expand All @@ -30,8 +30,8 @@ export function HeroSection() {
</Link>
</Button>

<Button asChild variant="outline" size="lg" className="inline-flex items-center gap-2 dark:text-zinc-50">
<a href="./#template">
<Button asChild variant="outline" size="lg" className="text-foreground">
<a href="#template">
<Clock className="h-4 w-4" />
Starter Kit — Coming Soon
</a>
Expand Down
2 changes: 1 addition & 1 deletion resources/js/components/landing/modules-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const modules: Module[] = [
export function ModulesSection() {
return (
<section className="relative z-10">
<div className="mr-auto ml-auto max-w-7xl pt-16 pr-6 pb-16 pl-6 lg:px-8">
<div className="mr-auto ml-auto max-w-7xl px-6 py-8 lg:py-12 lg:px-8">
<div className="relative overflow-hidden rounded-2xl bg-neutral-900 text-white dark:bg-neutral-100 dark:text-neutral-900">
{/* Decorative background */}
<div aria-hidden="true" className="pointer-events-none absolute inset-0">
Expand Down
2 changes: 1 addition & 1 deletion resources/js/components/landing/template-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const starterKitFeatures = [
export function TemplateSection() {
return (
<section className="relative z-10" id="template">
<div className="mx-auto max-w-7xl px-6 py-24 lg:px-8">
<div className="mx-auto max-w-7xl px-6 py-12 lg:py-20 lg:px-8">
<div className="mx-auto max-w-3xl text-center">
<h2 className="text-3xl font-semibold tracking-tight text-foreground">The Astrify Starter Kit</h2>
<p className="mt-3 text-base text-muted-foreground">
Expand Down
26 changes: 14 additions & 12 deletions resources/js/docs/json-table.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ This usage example loads data via JSON fetch requests. To load data via Inertia
<div>
<h1 className="text-xl font-bold">JSON Table Example</h1>
<p className="mb-4 text-sm">
This table uses JSON requests for it's data instead of Inertia requests. Useful when you need multiple tables on a single page
This table uses JSON requests for its data instead of Inertia requests. Useful when you need multiple tables on a single page
or don't want to tie the pagination to the rest of the page.
</p>
</div>
Expand Down Expand Up @@ -78,17 +78,19 @@ This usage example loads data via JSON fetch requests. To load data via Inertia
</ComponentSourceChild>

<ComponentSourceChild language="php" title="routes/web.php">
{`Route::get('/json-table-data-example', function () {
$users = \\App\\Models\\User::select('id', 'name', 'email', 'created_at')
->orderBy('id', 'desc')
->paginate(10);

return response()->json($users);
});

Route::get('/json-table-example', function () {
return Inertia::render('json-table-example');
});`}
{`Route::middleware(['auth'])->group(function () {
Route::get('/json-table-data-example', function () {
$users = \\App\\Models\\User::select('id', 'name', 'email', 'created_at')
->orderBy('id', 'desc')
->paginate(10);

return response()->json($users);
});

Route::get('/json-table-example', function () {
return Inertia::render('json-table-example');
});
});`}
</ComponentSourceChild>

Optionally seed your database with test data to see the pagination in action:
Expand Down
24 changes: 13 additions & 11 deletions resources/js/docs/paginated-table.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ This usage example loads data via Inertia requests. As you paginate this table t
<div>
<h1 className="text-xl font-bold">Inertia Table</h1>
<p className="mb-4 text-sm">
This table loads it's data via Inertia requests. As you paginate this table the page number is reflected in the address bar.
This table loads its data via Inertia requests. As you paginate this table the page number is reflected in the address bar.
</p>
</div>
<PaginatedTable
Expand All @@ -99,16 +99,18 @@ This usage example loads data via Inertia requests. As you paginate this table t
</ComponentSourceChild>

<ComponentSourceChild language="php" title="routes/web.php">
{`Route::get('inertia-table-example', function () {
$users = \\App\\Models\\User::select('id', 'name', 'email', 'created_at')
->orderBy('id', 'desc')
->paginate(10)
->onEachSide(1);

return Inertia::render('inertia-table-example', [
'users' => $users,
]);
})->name('table.index');`}
{`Route::middleware(['auth'])->group(function () {
Route::get('inertia-table-example', function () {
$users = \\App\\Models\\User::select('id', 'name', 'email', 'created_at')
->orderBy('id', 'desc')
->paginate(10)
->onEachSide(1);

return Inertia::render('inertia-table-example', [
'users' => $users,
]);
})->name('table.index');
});`}
</ComponentSourceChild>


Expand Down
75 changes: 44 additions & 31 deletions resources/js/docs/upload.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { Separator } from '@/components/ui/separator';
import { Upload, FileText, Cloud, Shield, Zap } from 'lucide-react';
import { UploadPreview, UploadPreviewSource, ImageUploadDemo, ImageUploadDemoSource } from '@/components/previews/upload-preview';

A complete file upload module for S3-compatible storage (Amazon S3, MinIO, DigitalOcean Spaces, etc.) with Laravel backend integration. Features drag & drop upload, real-time progress tracking, client-side file validation, and secure signed URL uploads.

## Multi-file Upload

Upload multiple files with drag & drop support, progress tracking, and error handling.
Expand Down Expand Up @@ -44,12 +46,11 @@ This command will install the necessary JavaScript dependencies, shadcn/ui compo
Reference the SignedUrlController in your routes file

```php
Route::post('/upload/signed-url',
\App\Http\Controllers\Astrify\SignedUrlController::class
)
// if the environment is public be sure to require auth
//->middleware(['auth'])
->name('upload.signed-url');
Route::middleware(['auth'])->group(function () {
Route::post('/upload/signed-url',
\App\Http\Controllers\Astrify\SignedUrlController::class
)->name('upload.signed-url');
});
```

### 3: Install Laravel Dependencies
Expand Down Expand Up @@ -234,34 +235,36 @@ This usage example provides a full page Inertia component. It integrates the upl
</ComponentSourceChild>

<ComponentSourceChild language="php" title="routes/web.php">
{`Route::get('/upload', function () {
return Inertia::render('inertia-upload-example');
})->name('upload.index');

Route::post('/upload', function (\\Illuminate\\Http\\Request $request) {
// Validate the incoming request
$validated = $request->validate([
'name' => 'required|string|max:255',
'uploadedFiles' => 'required|array|min:1',
'uploadedFiles.*.id' => 'required|string',
'uploadedFiles.*.name' => 'required|string',
'uploadedFiles.*.sha256' => 'required|string',
'uploadedFiles.*.size' => 'required|integer|min:0',
'uploadedFiles.*.type' => 'required|string',
]);

\\Illuminate\\Support\\Facades\\Log::info('Document creation request received', [
'name' => $validated['name'],
'uploaded_files' => $validated['uploadedFiles'],
]);

return back()->with('success', 'Files uploaded successfully');
})->name('upload.store');`}
{`Route::middleware(['auth'])->group(function () {
Route::get('/upload', function () {
return Inertia::render('inertia-upload-example');
})->name('upload.index');

Route::post('/upload', function (\\Illuminate\\Http\\Request $request) {
// Validate the incoming request
$validated = $request->validate([
'name' => 'required|string|max:255',
'uploadedFiles' => 'required|array|min:1',
'uploadedFiles.*.id' => 'required|string',
'uploadedFiles.*.name' => 'required|string',
'uploadedFiles.*.sha256' => 'required|string',
'uploadedFiles.*.size' => 'required|integer|min:0',
'uploadedFiles.*.type' => 'required|string',
]);

\\Illuminate\\Support\\Facades\\Log::info('Document creation request received', [
'name' => $validated['name'],
'uploaded_files' => $validated['uploadedFiles'],
]);

return back()->with('success', 'Files uploaded successfully');
})->name('upload.store');
});`}
</ComponentSourceChild>

## Security Notes

This module is designed to ensure secure file uploads to your S3 bucket. Heres an overview of the process:
This module is designed to ensure secure file uploads to your S3 bucket. Here's an overview of the process:

1. **Private bucket configuration:**
Your S3 bucket should be configured as **private**. This prevents users from uploading or accessing files directly and ensures all access goes through temporary signed URLs issued by your Laravel application.
Expand All @@ -284,4 +287,14 @@ This module is designed to ensure secure file uploads to your S3 bucket. Here’
6. **Tamper protection:**
If the client modifies any claims in the signed URL, the signature becomes invalid and the S3 storage will reject the upload.

> **Next Steps:** Try implementing the upload component in your application and customize it to match your specific requirements.
<Alert className="mt-8">
<Shield className="h-4 w-4" />
<AlertTitle>Secure Context Required</AlertTitle>
<AlertDescription>
This module uses the Web Crypto API for SHA256 hashing, which requires a **secure context**. The upload module will work on:
- ✅ `https://` (production with HTTPS)
- ✅ `http://localhost` or `http://127.0.0.1` (local development)
- ❌ `http://192.168.x.x` (local network over HTTP)
- ❌ Any non-HTTPS remote access
</AlertDescription>
</Alert>
2 changes: 1 addition & 1 deletion resources/js/layouts/docs-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default function DocsLayout({ slug, meta = {}, toc, manifest, children }:
<div data-slot="docs" className="px-0 sm:px-4 xl:px-0 flex items-stretch text-[1.05rem] sm:text-[15px] xl:w-full">
<div className="flex min-w-0 flex-1 flex-col">
<div className="h-(--top-spacing) shrink-0" />
<div className="mx-auto flex w-full max-w-2xl min-w-0 flex-1 flex-col gap-8 px-6 py-6 text-neutral-800 md:px-0 lg:py-8">
<div className="mx-auto flex w-full max-w-2xl min-w-0 flex-1 flex-col gap-8 px-6 py-6 text-foreground md:px-0 lg:py-8">
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-2">
<div className="flex items-start justify-between">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function InertiaTableExample({ users }: Props) {
<div>
<h1 className="text-xl font-bold">Inertia Table</h1>
<p className="mb-4 text-sm">
This table loads it's data via Inertia requests. As you paginate this table the page number is reflected in the address bar.
This table loads its data via Inertia requests. As you paginate this table the page number is reflected in the address bar.
</p>
</div>
<PaginatedTable
Expand Down
2 changes: 1 addition & 1 deletion resources/js/pages/astrify-examples/json-table-example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default function JsonTableExample() {
<div>
<h1 className="text-xl font-bold">JSON Table Example</h1>
<p className="mb-4 text-sm">
This table uses JSON requests for it's data instead of Inertia requests. Useful when you need multiple tables on a single page
This table uses JSON requests for its data instead of Inertia requests. Useful when you need multiple tables on a single page
or don't want to tie the pagination to the rest of the page.
</p>
</div>
Expand Down