Skip to content

Commit 69efb13

Browse files
committed
chore: Apply application layout to register snap page
1 parent de43e53 commit 69efb13

13 files changed

Lines changed: 550 additions & 444 deletions

File tree

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { ReactNode, useState } from "react";
2+
import { Link } from "react-router-dom";
3+
import { Notification } from "@canonical/react-components";
4+
5+
import RegisterSnapForm from "./RegisterSnapForm";
6+
7+
import { filterAvailableStores } from "../../utils";
8+
9+
import { useBrandStores } from "../../hooks";
10+
11+
import type { RegistrationResponse } from "./local-types";
12+
13+
function RegisterSnap(): ReactNode {
14+
const [isSending, setIsSending] = useState<boolean>(false);
15+
const [registrationResponse, setRegistrationResponse] =
16+
useState<RegistrationResponse>();
17+
const { data, isLoading } = useBrandStores();
18+
19+
const availableStores = filterAvailableStores(data || []);
20+
21+
return (
22+
<div className="u-fixed-width">
23+
{registrationResponse?.error_code === "already_registered" ? (
24+
<>
25+
<h1 className="p-heading--2">
26+
<strong>{registrationResponse.snap_name}</strong> is already taken
27+
</h1>
28+
29+
<Notification severity="caution">
30+
Another publisher already registered{" "}
31+
<strong>{registrationResponse.snap_name}</strong>. You can{" "}
32+
<Link
33+
to={`/register-name-dispute?snap-name=${registrationResponse.snap_name}&store=${registrationResponse.store}`}
34+
>
35+
file a dispute
36+
</Link>{" "}
37+
to request a transfer of ownership or register a new name below.
38+
</Notification>
39+
</>
40+
) : (
41+
<h1 className="p-heading--2">Register snap</h1>
42+
)}
43+
44+
{registrationResponse?.error_code === "name-review-required" && (
45+
<Notification severity="positive">
46+
<strong>{registrationResponse?.snap_name}</strong> has been submitted
47+
for review
48+
</Notification>
49+
)}
50+
51+
{registrationResponse?.error_code === "already_owned" && (
52+
<Notification severity="caution">
53+
You already own '
54+
<Link to={`/${registrationResponse.snap_name}/listing`}>
55+
<strong>{registrationResponse.snap_name}</strong>
56+
</Link>
57+
'.
58+
</Notification>
59+
)}
60+
61+
{registrationResponse?.error_code === "reserved_name" && (
62+
<Notification severity="caution">
63+
'<strong>{registrationResponse.snap_name}</strong>' is reserved. You
64+
can{" "}
65+
<a
66+
href={`/request-reserved-name?snap_name=${registrationResponse.snap_name}&store=${registrationResponse.store}&is_private=${registrationResponse.is_private}`}
67+
>
68+
request a reserved name
69+
</a>{" "}
70+
or register a new name below.
71+
</Notification>
72+
)}
73+
74+
{registrationResponse?.error_code !== "already_registered" && (
75+
<p>
76+
Before you can push your snap to the store, its name must be
77+
registered
78+
</p>
79+
)}
80+
81+
<Notification severity="information">
82+
Snap name registrations are subject to manual review. You will be able
83+
to upload your snap and update its metadata, but you will not be able to
84+
make the Snap public until the review has been completed. We aim to
85+
review all registrations within 30 days
86+
</Notification>
87+
88+
{!isLoading && availableStores.length > 0 && (
89+
<RegisterSnapForm
90+
isSending={isSending}
91+
setIsSending={setIsSending}
92+
setRegistrationResponse={setRegistrationResponse}
93+
availableStores={availableStores}
94+
/>
95+
)}
96+
</div>
97+
);
98+
}
99+
100+
export default RegisterSnap;
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { ReactNode, useState, Dispatch, SetStateAction } from "react";
2+
import { Link, useNavigate } from "react-router-dom";
3+
import {
4+
Form,
5+
Select,
6+
Input,
7+
Button,
8+
Icon,
9+
Row,
10+
Col,
11+
} from "@canonical/react-components";
12+
13+
import type { RegistrationResponse } from "./local-types";
14+
15+
type Props = {
16+
isSending: boolean;
17+
setIsSending: Dispatch<SetStateAction<boolean>>;
18+
setRegistrationResponse: Dispatch<
19+
SetStateAction<RegistrationResponse | undefined>
20+
>;
21+
availableStores: { name: string; id: string }[];
22+
};
23+
24+
function RegisterSnapForm({
25+
isSending,
26+
setIsSending,
27+
setRegistrationResponse,
28+
availableStores,
29+
}: Props): ReactNode {
30+
const [snapName, setSnapName] = useState<string>();
31+
const [selectedStore, setSelectedStore] = useState<string>("ubuntu");
32+
const [isPrivate, setIsPrivate] = useState<string>("private");
33+
34+
const navigate = useNavigate();
35+
36+
return (
37+
<Form
38+
onSubmit={async (e) => {
39+
e.preventDefault();
40+
41+
setIsSending(true);
42+
43+
const formData = new FormData();
44+
formData.set("csrf_token", window.CSRF_TOKEN);
45+
formData.set("snap_name", snapName || "");
46+
formData.set("store", selectedStore);
47+
48+
const response = await fetch("/api/register-snap", {
49+
method: "POST",
50+
headers: {
51+
"X-CSRFToken": window.CSRF_TOKEN,
52+
},
53+
body: formData,
54+
});
55+
56+
if (!response.ok) {
57+
setIsSending(false);
58+
throw new Error("Unable to register snap name");
59+
}
60+
61+
const responseData = await response.json();
62+
63+
setTimeout(() => {
64+
if (responseData.success) {
65+
navigate("/snaps");
66+
} else {
67+
setRegistrationResponse(responseData.data);
68+
setIsSending(false);
69+
}
70+
}, 1000);
71+
}}
72+
>
73+
<Row>
74+
<Col size={8}>
75+
<Select
76+
label="Store"
77+
options={availableStores.map((store) => ({
78+
label: store.name,
79+
value: store.id,
80+
}))}
81+
onChange={(e) => {
82+
setSelectedStore(e.target.value);
83+
84+
if (e.target.value === "ubuntu") {
85+
setIsPrivate("private");
86+
}
87+
}}
88+
required
89+
/>
90+
<Input
91+
type="text"
92+
label="Snap name"
93+
defaultValue={snapName}
94+
onChange={(e) => {
95+
setSnapName(e.target.value);
96+
}}
97+
required
98+
/>
99+
<label htmlFor="public">Snap privacy</label>
100+
<p className="p-form-help-text">
101+
This can be changed at any time after the initial upload
102+
</p>
103+
<Input
104+
type="radio"
105+
label="Public"
106+
name="public"
107+
disabled={selectedStore === "ubuntu"}
108+
value="public"
109+
checked={isPrivate === "public"}
110+
onChange={(e) => {
111+
setIsPrivate(e.target.value);
112+
}}
113+
/>
114+
<Input
115+
type="radio"
116+
label="Private"
117+
name="public"
118+
help="Snap is hidden in stores and only accessible by the publisher and collaborators"
119+
disabled={selectedStore === "ubuntu"}
120+
value="private"
121+
checked={isPrivate === "private" || selectedStore === "ubuntu"}
122+
onChange={(e) => {
123+
setIsPrivate(e.target.value);
124+
}}
125+
/>
126+
</Col>
127+
</Row>
128+
<hr />
129+
<div className="u-align--right">
130+
<Button
131+
type="submit"
132+
appearance="positive"
133+
disabled={!snapName || isSending}
134+
>
135+
{isSending ? (
136+
<>
137+
<Icon name="spinner" className="u-animation--spin" />
138+
&nbsp;Registering
139+
</>
140+
) : (
141+
<>Register</>
142+
)}
143+
</Button>
144+
<Link className="p-button" to="/snaps">
145+
Cancel
146+
</Link>
147+
</div>
148+
</Form>
149+
);
150+
}
151+
152+
export default RegisterSnapForm;

0 commit comments

Comments
 (0)