import { useQuery, useQueryClient } from '@tanstack/react-query'; import { GlobeIcon, LockIcon } from 'lucide-react'; import { useState } from 'react'; import { Button } from '#/components/atoms/button.jsx'; import { Input } from '#/components/atoms/input.jsx'; import { Label } from '#/components/atoms/label.jsx'; import { Textarea } from '#/components/atoms/textarea.jsx'; import { Screen } from '#/components/molecules/screen.jsx'; import { createRepo } from '#/lib/create.js'; import { account, loadRepos } from '#/lib/git.js'; import { useNavigate } from '#/lib/navigation.jsx'; import { useSession } from '#/lib/session.jsx'; import { spaceSupport } from '#/lib/spaceSupport.js'; import { cn } from '#/lib/utils.js'; /** The characters a repository name may hold, from the server's own rule. */ const NAME = /^[A-Za-z0-9._~-]{1,512}$/; /** The two visibilities, each with the sentence that says what it means. */ const VISIBILITIES = [ { value: 'public', label: 'Public', Icon: GlobeIcon, says: 'Anyone on the network can read it.', }, { value: 'private', label: 'Private', Icon: LockIcon, says: 'Only people you add can read it.', }, ]; /** * Creating a repository, on a page of its own. * * The forge is one account's, so the owner is the account and the form only * asks for the name, a description and the visibility. A private repository * lives in a permissioned space, which the account's own PDS has to serve; * the form offers one only when it does. The space itself needs nothing: one * is created for the repository, keyed by its name, and members join from the * repository page. */ export function NewRepoPage() { const { session } = useSession(); const queryClient = useQueryClient(); const navigate = useNavigate(); const [name, setName] = useState(''); const [description, setDescription] = useState(''); const [visibility, setVisibility] = useState('public'); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); // Whether the account's PDS serves the spaces a private repository needs. const { data: supported } = useQuery({ queryKey: ['space-support'], queryFn: spaceSupport, staleTime: Number.POSITIVE_INFINITY, }); const mine = Boolean(session && session.did === account.did); const validName = NAME.test(name) && name !== '.' && name !== '..'; const submit = async (event) => { event.preventDefault(); if (!validName || !mine) return; setBusy(true); setError(''); try { const payload = visibility === 'private' ? { name, description, private: true } : { name, description }; await createRepo( /** @type {import('#/lib/oauth.js').Session} */ (session), payload, ); // The record the new page reads from is in hand before the page opens, // the way a merge reloads before the reads that hang off it are. await loadRepos(); await queryClient.invalidateQueries(); navigate(`/${encodeURIComponent(name)}`); } catch (err) { setError(err instanceof Error ? err.message : String(err)); setBusy(false); } }; return ( {/* A form stands on its own, centred, the way a forge's does: nothing on this page but the question it asks. */}

Create a new repository

A repository is a record in {account.handle}'s own repo, with its bundles in blob storage. Push to it with the git helper once it exists.

{!mine ? (

Creating writes the record into {account.handle}'s own repo, so it needs this account's sign-in rather than permission from anybody else. Sign in as {account.handle} from the bar above.

) : (
{account.handle} / setName(event.target.value)} placeholder="my-project" pattern="[A-Za-z0-9._~\-]{1,512}" autoFocus disabled={busy} />
{name && (

atproto://{account.handle}/{name}

)}