Skip to content

Commit 46a27c8

Browse files
committed
feat: add create repo page
1 parent edd3e52 commit 46a27c8

File tree

20 files changed

+1041
-24
lines changed

20 files changed

+1041
-24
lines changed

apps/gitness/src/App.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import i18n from './i18n/i18n'
3030
import PipelineLayout from './layouts/PipelineStudioLayout'
3131
import PullRequestLayout from './layouts/PullRequestLayout'
3232
import RepoLayoutV1 from './layouts/RepoLayout'
33+
import { CreateRepo } from './pages-v2/repo/repo-create-page'
3334
import RepoLayout from './pages-v2/repo/repo-layout'
3435
import ReposListPage from './pages-v2/repo/repo-list'
3536
import { RepoSidebar } from './pages-v2/repo/repo-sidebar'
@@ -57,7 +58,7 @@ import PullRequestListPage from './pages/pull-request/pull-request-list-page'
5758
import { RepoBranchesListPage } from './pages/repo/repo-branch-list'
5859
import { RepoBranchSettingsRulesPageContainer } from './pages/repo/repo-branch-rules-container'
5960
import RepoCommitsPage from './pages/repo/repo-commits'
60-
import { CreateRepo } from './pages/repo/repo-create-page'
61+
import { CreateRepoV1 } from './pages/repo/repo-create-page'
6162
import { RepoFiles } from './pages/repo/repo-files'
6263
import { RepoHeader } from './pages/repo/repo-header'
6364
import { RepoImportContainer } from './pages/repo/repo-import-container'
@@ -130,6 +131,14 @@ export default function App() {
130131
}
131132
]
132133
},
134+
{
135+
path: ':spaceId/repos/create',
136+
element: <CreateRepo />
137+
},
138+
{
139+
path: ':spaceId/repos/import',
140+
element: <RepoImportContainer />
141+
},
133142
{
134143
path: 'theme',
135144
element: <ProfileSettingsThemePage />
@@ -395,7 +404,7 @@ export default function App() {
395404
},
396405
{
397406
path: ':spaceId/repos/create',
398-
element: <CreateRepo />
407+
element: <CreateRepoV1 />
399408
},
400409
{
401410
path: ':spaceId/repos/import',
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { useState } from 'react'
2+
import { useNavigate } from 'react-router-dom'
3+
4+
import {
5+
CreateRepositoryErrorResponse,
6+
OpenapiCreateRepositoryRequest,
7+
useCreateRepositoryMutation,
8+
useListGitignoreQuery,
9+
useListLicensesQuery
10+
} from '@harnessio/code-service-client'
11+
import { RepoCreatePage as RepoCreatePageView } from '@harnessio/ui/views'
12+
import { FormFields } from '@harnessio/views'
13+
14+
import { useGetSpaceURLParam } from '../../framework/hooks/useGetSpaceParam'
15+
16+
export const CreateRepo = () => {
17+
const createRepositoryMutation = useCreateRepositoryMutation({})
18+
const spaceId = useGetSpaceURLParam()
19+
const navigate = useNavigate()
20+
const [apiError, setApiError] = useState<string | null>(null)
21+
22+
const onSubmit = (data: FormFields) => {
23+
const repositoryRequest: OpenapiCreateRepositoryRequest = {
24+
default_branch: 'main',
25+
parent_ref: spaceId,
26+
description: data.description,
27+
git_ignore: data.gitignore,
28+
license: data.license,
29+
is_public: data.access === '1',
30+
readme: true,
31+
identifier: data.name
32+
}
33+
34+
createRepositoryMutation.mutate(
35+
{
36+
queryParams: {},
37+
body: repositoryRequest
38+
},
39+
{
40+
onSuccess: ({ body: data }) => {
41+
setApiError(null)
42+
navigate(`/spaces/${spaceId}/repos/${data?.identifier}`)
43+
},
44+
onError: (error: CreateRepositoryErrorResponse) => {
45+
const message = error.message || 'An unknown error occurred.'
46+
setApiError(message)
47+
}
48+
}
49+
)
50+
}
51+
52+
const { data: { body: gitIgnoreOptions } = {} } = useListGitignoreQuery({})
53+
54+
const { data: { body: licenseOptions } = {} } = useListLicensesQuery({})
55+
56+
const onCancel = () => {
57+
navigate(`/spaces/${spaceId}/repos`)
58+
}
59+
60+
return (
61+
<>
62+
<RepoCreatePageView
63+
onFormSubmit={onSubmit}
64+
onFormCancel={onCancel}
65+
apiError={apiError}
66+
isLoading={createRepositoryMutation.isLoading}
67+
isSuccess={createRepositoryMutation.isSuccess}
68+
gitIgnoreOptions={gitIgnoreOptions}
69+
licenseOptions={licenseOptions}
70+
/>
71+
</>
72+
)
73+
}

apps/gitness/src/pages/repo/repo-create-page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { FormFields, RepoCreatePageForm } from '@harnessio/views'
1212

1313
import { useGetSpaceURLParam } from '../../framework/hooks/useGetSpaceParam'
1414

15-
export const CreateRepo = () => {
15+
export const CreateRepoV1 = () => {
1616
const createRepositoryMutation = useCreateRepositoryMutation({})
1717
const spaceId = useGetSpaceURLParam()
1818
const navigate = useNavigate()

packages/ui/src/components/button.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ const buttonVariants = cva(
1010
{
1111
variants: {
1212
variant: {
13-
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
14-
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
15-
outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
16-
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
17-
tertiary: 'bg-tertiary text-secondary-foreground shadow-sm hover:bg-tertiary/80',
13+
default: 'bg-primary text-primary-foreground hover:bg-primary/90 shadow',
14+
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-sm',
15+
outline: 'border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-sm',
16+
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-sm',
17+
tertiary: 'bg-tertiary text-secondary-foreground hover:bg-tertiary/80 shadow-sm',
1818
ghost: 'hover:bg-accent hover:text-accent-foreground',
1919
link: 'text-primary underline-offset-4 hover:underline',
2020
link_accent: 'text-foreground-accent underline-offset-4 hover:underline',
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import * as React from 'react'
2+
3+
import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
4+
import { CheckIcon } from '@radix-ui/react-icons'
5+
import { cn } from '@utils/cn'
6+
7+
import { Label } from './label'
8+
9+
interface CheckboxProps extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> {
10+
label?: string
11+
}
12+
13+
const Checkbox = React.forwardRef<React.ElementRef<typeof CheckboxPrimitive.Root>, CheckboxProps>(
14+
({ className, label, ...props }, ref) => (
15+
<div className={cn('flex gap-x-2.5', className)}>
16+
<CheckboxPrimitive.Root
17+
ref={ref}
18+
className="border-icons-1 focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:border-icons-2 data-[state=checked]:text-primary-foreground peer size-4 shrink-0 rounded-sm border shadow focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50"
19+
{...props}
20+
>
21+
<CheckboxPrimitive.Indicator className={cn('flex items-center justify-center text-current')}>
22+
<CheckIcon className="size-4" />
23+
</CheckboxPrimitive.Indicator>
24+
</CheckboxPrimitive.Root>
25+
{label && (
26+
<Label className="text-foreground-1 font-normal leading-tight tracking-tight" htmlFor={props.id}>
27+
{label}
28+
</Label>
29+
)}
30+
</div>
31+
)
32+
)
33+
Checkbox.displayName = CheckboxPrimitive.Root.displayName
34+
35+
export { Checkbox }
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import { cn } from '@utils/cn'
2+
3+
import { Checkbox, RadioGroupItem, Label as ShadLabel, Text } from './'
4+
5+
interface CompProps {
6+
children: React.ReactNode
7+
className?: string
8+
}
9+
10+
interface RootProps {
11+
children: React.ReactNode
12+
box?: boolean
13+
shaded?: boolean
14+
className?: string
15+
}
16+
17+
enum MessageTheme {
18+
SUCCESS = 'success',
19+
WARNING = 'warning',
20+
ERROR = 'error',
21+
DEFAULT = 'default'
22+
}
23+
24+
interface MessageProps {
25+
children: React.ReactNode
26+
className?: string
27+
theme: MessageTheme
28+
}
29+
30+
interface ControlProps {
31+
children: React.ReactNode
32+
type?: 'button'
33+
className?: string
34+
}
35+
36+
type ControlType = React.ReactElement<typeof RadioGroupItem> | React.ReactElement<typeof Checkbox>
37+
38+
interface OptionProps {
39+
control: ControlType
40+
id: string
41+
label?: string
42+
description?: string
43+
className?: string
44+
}
45+
46+
interface LabelProps {
47+
children: React.ReactNode
48+
htmlFor?: string
49+
optional?: boolean
50+
className?: string
51+
}
52+
53+
interface SeparatorProps {
54+
className?: string
55+
dashed?: boolean
56+
dotted?: boolean
57+
}
58+
59+
interface SpacerProps {
60+
className?: string
61+
}
62+
63+
const themeClassMap: Record<MessageTheme, string> = {
64+
[MessageTheme.SUCCESS]: 'text-success',
65+
[MessageTheme.WARNING]: 'text-warning',
66+
[MessageTheme.ERROR]: 'text-foreground-danger',
67+
[MessageTheme.DEFAULT]: 'text-tertiary-background'
68+
}
69+
70+
function Root({ children, box, shaded, className }: RootProps) {
71+
return (
72+
<fieldset
73+
className={cn(
74+
'flex flex-col',
75+
{ 'rounded-md border px-5 py-3.5 pb-5': box, 'bg-primary/[0.02]': shaded },
76+
className
77+
)}
78+
role="group"
79+
aria-describedby="fieldset-description"
80+
>
81+
{children}
82+
</fieldset>
83+
)
84+
}
85+
86+
function Legend({ children, className }: CompProps) {
87+
return (
88+
<Text size={3} weight={'medium'} className={cn('mb-0', className)} as="p" role="heading">
89+
{children}
90+
</Text>
91+
)
92+
}
93+
94+
function SubLegend({ children, className }: CompProps) {
95+
return (
96+
<Text size={2} weight={'normal'} className={cn('text-primary/70 mb-0', className)} as="p" id="fieldset-description">
97+
{children}
98+
</Text>
99+
)
100+
}
101+
102+
function Item({ children, className }: CompProps) {
103+
return (
104+
<div className={cn('item-wrapper', className)} role="presentation">
105+
{children}
106+
</div>
107+
)
108+
}
109+
110+
function Label({ htmlFor, optional, children, className }: LabelProps) {
111+
return (
112+
<ShadLabel
113+
htmlFor={htmlFor}
114+
variant="default"
115+
className={cn('text-foreground-2 leading-none font-normal mb-2.5', className)}
116+
>
117+
{children} {optional && <span className="text-foreground-7 align-top">(optional)</span>}
118+
</ShadLabel>
119+
)
120+
}
121+
122+
function ControlGroup({ children, type, className }: ControlProps) {
123+
return (
124+
<div
125+
className={cn('relative flex flex-col', { 'mt-2': type === 'button' }, className)}
126+
role="group"
127+
aria-label={type === 'button' ? 'Button control group' : 'Input control group'}
128+
>
129+
{children}
130+
</div>
131+
)
132+
}
133+
134+
function Caption({ children, className }: CompProps) {
135+
return (
136+
<Text
137+
className={cn('mt-2 tracking-tight leading-none', className)}
138+
as="p"
139+
size={2}
140+
color="tertiaryBackground"
141+
role="note"
142+
aria-live="polite"
143+
>
144+
{children}
145+
</Text>
146+
)
147+
}
148+
149+
function Message({ children, theme, className }: MessageProps) {
150+
const textClass = themeClassMap[theme]
151+
const role = theme === MessageTheme.ERROR ? 'alert' : 'status'
152+
const ariaLive = theme === MessageTheme.ERROR ? 'assertive' : 'polite'
153+
154+
return (
155+
<div className={cn('absolute top-full', textClass, className)} role={role} aria-live={ariaLive}>
156+
<Text as="p" size={0} className="font-light tracking-tight text-inherit">
157+
{children}
158+
</Text>
159+
</div>
160+
)
161+
}
162+
163+
function Option({ control, id, label, description, className }: OptionProps) {
164+
return (
165+
<div className={cn('flex items-start gap-x-4', className)} role="option" aria-labelledby={`${id}-label`}>
166+
{control}
167+
<div className="flex flex-col gap-0">
168+
<Label htmlFor={id} className="font-medium">
169+
{label}
170+
</Label>
171+
{description && (
172+
<Text
173+
className="leading-none tracking-tight"
174+
as="p"
175+
size={2}
176+
color="tertiaryBackground"
177+
id={`${id}-description`}
178+
role="note"
179+
>
180+
{description}
181+
</Text>
182+
)}
183+
</div>
184+
</div>
185+
)
186+
}
187+
188+
function Separator({ dashed, dotted, className }: SeparatorProps) {
189+
return (
190+
<div
191+
className={cn('border-b', { 'border-dashed': dashed, 'border-dotted': dotted }, className)}
192+
role="separator"
193+
aria-orientation="horizontal"
194+
/>
195+
)
196+
}
197+
198+
function Spacer({ className }: SpacerProps) {
199+
return <div className={cn('mt-1', className)} role="presentation" aria-hidden="true" />
200+
}
201+
202+
export { Root, Legend, SubLegend, Item, Label, ControlGroup, Caption, Message, Option, Separator, Spacer, MessageTheme }

packages/ui/src/components/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,15 @@ export * from './tabs'
2525
export * from './branch-selector'
2626
export * from './command'
2727
export * from './search-files'
28+
export * from './input'
29+
export * from './radio-group'
30+
export * from './textarea'
31+
export * from './checkbox'
32+
export * as FormFieldSet from './form-field-set'
2833
export * as ListActions from './list-actions'
2934
export * as ListPagination from './list-pagination'
3035
export * as SearchBox from './search-box'
3136
export * as NavbarProjectChooser from './navbar-project-chooser'
3237
export * as StackedList from './stacked-list'
3338
export * as ButtonGroup from './button-group'
39+
export * as Toast from './toast'

0 commit comments

Comments
 (0)