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
2 changes: 1 addition & 1 deletion src/components/organization/OrganizationHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const OrganizationHeader: React.FC<OrganizationHeaderProps> = ({
}
return (
<div className={cn("bg-white border-b border-gray-200", className)}>
<div className="container mx-auto px-6 py-6">
<div className="container lg:mx-auto lg:px-6 py-6">
{/* Mobile Layout */}
<div className="block sm:hidden">
<div className="flex flex-col items-center text-center space-y-4">
Expand Down
63 changes: 34 additions & 29 deletions src/pages/create-organization.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from "react"
import { useLocation } from "wouter"
import { Redirect, useLocation } from "wouter"
import { Helmet } from "react-helmet-async"
import Header from "@/components/Header"
import { Button } from "@/components/ui/button"
Expand All @@ -8,17 +8,18 @@ import { Label } from "@/components/ui/label"
import toast from "react-hot-toast"
import { useCreateOrgMutation } from "@/hooks/use-create-org-mutation"
import { normalizeName } from "@/lib/utils/normalizeName"
import { useGlobalStore } from "@/hooks/use-global-store"

interface FormErrors {
name?: string
handle?: string
display_name?: string
}

export const CreateOrganizationPage = () => {
const [, setLocation] = useLocation()

const session = useGlobalStore((s) => s.session)
const [formData, setFormData] = useState({
name: "",
handle: "",
display_name: "",
})

Expand All @@ -29,28 +30,28 @@ export const CreateOrganizationPage = () => {
useCreateOrgMutation({
onSuccess: (newOrganization) => {
toast.success(
`Organization "${newOrganization.display_name || newOrganization.name}" created successfully!`,
`Organization "${newOrganization.display_name || newOrganization.tscircuit_handle}" created successfully!`,
)
setLocation(`/${newOrganization.name}`)
setLocation(`/${newOrganization.tscircuit_handle}`)
setIsLoading(false)
},
})

const validateForm = (): boolean => {
const newErrors: FormErrors = {}

if (!formData.name) {
newErrors.name = "Organization name is required"
} else if (formData.name.length > 40) {
newErrors.name = "Organization name must be less than 40 characters"
} else if (formData.name.length < 5) {
newErrors.name = "Organization name must be at least 5 characters"
if (!formData.handle) {
newErrors.handle = "Organization handle is required"
} else if (formData.handle.length > 40) {
newErrors.handle = "Organization handle must be less than 40 characters"
} else if (formData.handle.length < 5) {
newErrors.handle = "Organization handle must be at least 5 characters"
}

const normalizedName = normalizeName(formData.name)
const normalizedName = normalizeName(formData.handle)
if (normalizedName.length < 5) {
newErrors.name =
"Organization name must be at least 5 characters after normalization"
newErrors.handle =
"Organization handle must be at least 5 characters after normalization"
}

if (formData.display_name) {
Expand All @@ -72,8 +73,8 @@ export const CreateOrganizationPage = () => {
return
}

const normalizedName = normalizeName(formData.name)
const displayName = formData.display_name || formData.name
const normalizedName = normalizeName(formData.handle)
const displayName = formData.display_name || formData.handle

setIsLoading(true)
createOrganization(
Expand All @@ -96,10 +97,10 @@ export const CreateOrganizationPage = () => {
}

const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const name = e.target.value
setFormData((prev) => ({ ...prev, name }))
if (errors.name) {
setErrors((prev) => ({ ...prev, name: undefined }))
const handle = e.target.value
setFormData((prev) => ({ ...prev, handle }))
if (errors.handle) {
setErrors((prev) => ({ ...prev, handle: undefined }))
}
}

Expand All @@ -111,6 +112,10 @@ export const CreateOrganizationPage = () => {
}
}

if (!session) {
return <Redirect to="/" />
}

return (
<div className="min-h-screen bg-white">
<Helmet>
Expand Down Expand Up @@ -140,32 +145,32 @@ export const CreateOrganizationPage = () => {
htmlFor="org-name"
className="text-sm font-semibold text-gray-900"
>
Organization Name
Organization Handle
<span className="text-red-500">*</span>
</Label>
<Input
spellCheck={false}
id="org-name"
type="text"
placeholder="My Organization"
value={formData.name}
value={formData.handle}
onChange={handleNameChange}
className={`h-10 sm:h-11 ${
errors.name
errors.handle
? "border-red-300 focus:border-red-500 focus:ring-red-500"
: "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
}`}
disabled={isLoading || isMutating}
/>
{errors.name && (
<p className="text-sm text-red-600">{errors.name}</p>
{errors.handle && (
<p className="text-sm text-red-600">{errors.handle}</p>
)}
<p className="text-xs text-gray-500">
This will be your URL.
<br />
<span className="font-mono text-gray-700">
tscircuit.com/
{normalizeName(formData.name) || "my-organization"}
{normalizeName(formData.handle) || "my-organization"}
</span>
</p>
</div>
Expand Down Expand Up @@ -195,15 +200,15 @@ export const CreateOrganizationPage = () => {
<p className="text-sm text-red-600">{errors.display_name}</p>
)}
<p className="text-xs text-gray-500">
Optional. If not provided, your organization name will be used
Optional. If not provided, your organization handle will be used
as the display name.
</p>
</div>

<div>
<Button
type="submit"
disabled={isLoading || isMutating || !formData.name}
disabled={isLoading || isMutating || !formData.handle}
className="w-full h-10 sm:h-11 bg-blue-600 hover:bg-blue-700 text-white font-medium text-sm sm:text-base"
>
{isLoading || isMutating ? (
Expand Down