Files
ignacionelson 6e47d76ba6 ProjectSend 2.0.0
Client file sharing, rebuilt from the ground up: a private area per
client, resumable uploads, folders, groups and categories, sharing with
expiry dates and download limits, comments, file versions, an activity
log, a REST API, and sixteen languages.

This repository begins here. ProjectSend 2 was developed privately, and
that development history is not published — the previous generation
remains available, with its own history, at projectsend/legacy.

Free software under the GNU General Public License v2, or (at your
option) any later version.
2026-08-14 01:38:12 -03:00

130 lines
5.2 KiB
TypeScript

import { Head, useForm } from '@inertiajs/react';
import { LoaderCircle } from 'lucide-react';
import { FormEventHandler } from 'react';
import InputError from '@/components/input-error';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { PasswordRequirements } from '@/components/password-requirements';
import { useTranslation } from '@/hooks/use-translation';
import AuthLayout from '@/layouts/auth-layout';
interface SetupForm {
[key: string]: string;
site_name: string;
name: string;
email: string;
password: string;
password_confirmation: string;
}
export default function Setup() {
const { t } = useTranslation();
const { data, setData, post, processing, errors, reset } = useForm<SetupForm>({
site_name: 'ProjectSend',
name: '',
email: '',
password: '',
password_confirmation: '',
});
const submit: FormEventHandler = (e) => {
e.preventDefault();
post(route('setup.store'), {
onFinish: () => reset('password', 'password_confirmation'),
});
};
return (
<AuthLayout
title={t('Welcome to ProjectSend')}
description={t('This installation has no administrator yet. Create the first one to get started.')}
>
<Head title={t('Set up ProjectSend')} />
<form className="flex flex-col gap-6" onSubmit={submit}>
<div className="grid gap-6">
<div className="grid gap-2">
<Label htmlFor="site_name">{t('Site name')}</Label>
<Input
id="site_name"
type="text"
required
autoFocus
tabIndex={1}
value={data.site_name}
onChange={(e) => setData('site_name', e.target.value)}
/>
<p className="text-muted-foreground text-sm">{t('Shown across the application and to your clients.')}</p>
<InputError message={errors.site_name} />
</div>
<div className="grid gap-2">
<Label htmlFor="name">{t('Name')}</Label>
<Input
id="name"
type="text"
required
tabIndex={2}
autoComplete="name"
value={data.name}
onChange={(e) => setData('name', e.target.value)}
/>
<InputError message={errors.name} />
</div>
<div className="grid gap-2">
<Label htmlFor="email">{t('Email address')}</Label>
<Input
id="email"
type="email"
required
tabIndex={3}
autoComplete="email"
value={data.email}
onChange={(e) => setData('email', e.target.value)}
placeholder="email@example.com"
/>
<InputError message={errors.email} />
</div>
<div className="grid gap-2">
<Label htmlFor="password">{t('Password')}</Label>
<Input
id="password"
type="password"
required
tabIndex={4}
autoComplete="new-password"
value={data.password}
onChange={(e) => setData('password', e.target.value)}
/>
<PasswordRequirements />
<InputError message={errors.password} />
</div>
<div className="grid gap-2">
<Label htmlFor="password_confirmation">{t('Confirm password')}</Label>
<Input
id="password_confirmation"
type="password"
required
tabIndex={5}
autoComplete="new-password"
value={data.password_confirmation}
onChange={(e) => setData('password_confirmation', e.target.value)}
/>
<InputError message={errors.password_confirmation} />
</div>
<Button type="submit" className="mt-2 w-full" tabIndex={6} disabled={processing}>
{processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
{t('Create administrator account')}
</Button>
</div>
</form>
</AuthLayout>
);
}