Skip to content

Commit a6806d2

Browse files
committed
feat(webapp): add a "add project" page
This commit split the "Projects" component in two smaller ones, and get rid of the form to add a project. Instead, this commit introduces a separate page for creating projects.
1 parent 0d639c7 commit a6806d2

File tree

7 files changed

+355
-100
lines changed

7 files changed

+355
-100
lines changed

typhon-webapp/src/app.rs

+22-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ pub fn App() -> impl IntoView {
5353
margin: 0;
5454
padding: 0;
5555
}
56+
:deep(*) {
57+
box-sizing: border-box;
58+
}
5659
:deep(:root) {
5760
--font-size-huge: 20px;
5861
--font-size-big: 16px;
@@ -75,6 +78,7 @@ pub fn App() -> impl IntoView {
7578
--color-red: #cf222e;
7679
--color-lightred: #d1242f;
7780
--color-green: #1a7f37;
81+
--color-darkgreen: rgb(24, 119, 51);
7882
--color-lightgreen: #1f883d;
7983
--color-orange: rgb(219, 171, 10);
8084

@@ -136,13 +140,30 @@ pub fn App() -> impl IntoView {
136140
:deep(.is-table .rows > .row:last-child) {
137141
border-radius: 0 0 var(--radius) var(--radius);
138142
}
143+
:deep(input[type=text]:focus), :deep(input[type=text]:focus-visible) {
144+
outline: var(--color-bg-emphasis) auto 1px;
145+
outline-offset: 0px;
146+
}
147+
:deep(input[type=text]) {
148+
border: 1px solid var(--color-border-default);
149+
border-radius: 3px;
150+
font-size: inherit;
151+
font-family: inherit;
152+
padding: 6px 10px;
153+
margin-top: 4px;
154+
margin-bottom: 4px;
155+
font-size: inherit;
156+
font-weight: inherit;
157+
}
139158
};
140159
provide_context(utils::now_signal());
141160
view! { class=_styler_class,
142161
<Router>
143162
<Style>{include_str!("../../target/main.css")}</Style>
144163
<Stylesheet href="/assets/node_modules/@fontsource/jetbrains-mono/index.css"/>
145-
<Stylesheet href="/assets/node_modules/@fontsource/roboto/index.css"/>
164+
<Stylesheet href="/assets/node_modules/@fontsource/roboto/300.css"/>
165+
<Stylesheet href="/assets/node_modules/@fontsource/roboto/400.css"/>
166+
<Stylesheet href="/assets/node_modules/@fontsource/roboto/500.css"/>
146167
<Routes>
147168
<Route path="/*any" view=routes::Router ssr=SsrMode::Async/>
148169
</Routes>

typhon-webapp/src/components/header.rs

+31-13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub fn TyphonLogo() -> impl IntoView {
77
a {
88
display: inline-flex;
99
align-items: center;
10+
justify-content: normal;
1011
padding: 8px;
1112
user-select: none;
1213
}
@@ -115,29 +116,46 @@ pub fn Nav(route: Signal<Option<routes::Root<routes::Empty>>>) -> impl IntoView
115116
#[component]
116117
pub fn Header(#[prop(into)] route: Signal<Option<routes::Root<routes::Empty>>>) -> impl IntoView {
117118
let style = style! {
118-
div {
119+
.nav-wrapper {
119120
border-bottom: 1px solid var(--color-border-default);
120121
display: flex;
121122
align-items: center;
123+
justify-content: space-between;
122124
background: var(--color-lllightgray);
123125
}
126+
.buttons {
127+
justify-content: normal;
128+
gap: 10px;
129+
display: flex;
130+
padding-right: 8px;
131+
}
124132
};
125133
let user: Signal<Option<typhon_types::data::User>> = use_context().unwrap();
126-
view! {
127-
<div class=style>
134+
view! { class=style,
135+
<div class="nav-wrapper">
128136
<TyphonLogo/>
129137
<Nav route/>
130-
<Transition fallback=move || {
131-
view! { <span>"Loading..."</span> }
132-
}>
133-
{move || {
134-
match user() {
135-
Some(_) => view! { <login::Logout></login::Logout> }.into_view(),
136-
None => view! { <A href="/login">"Log In"</A> }.into_view(),
137-
}
138-
}}
138+
<div class="buttons">
139+
<Transition fallback=move || {
140+
view! { <span>"Loading..."</span> }
141+
}>
142+
{move || {
143+
match user() {
144+
Some(_) => {
145+
view! {
146+
<A href=String::from(Root::AddProject)>
147+
<button>Add project</button>
148+
</A>
149+
<login::Logout></login::Logout>
150+
}
151+
.into_view()
152+
}
153+
None => view! { <A href="/login">"Log In"</A> }.into_view(),
154+
}
155+
}}
139156

140-
</Transition>
157+
</Transition>
158+
</div>
141159
</div>
142160
}
143161
}
+215
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
use crate::prelude::*;
2+
3+
#[component]
4+
pub(crate) fn AddProject() -> impl IntoView {
5+
let action = request_action!(
6+
CreateProject,
7+
|name: String, url: String, flake: Option<String>| requests::Request::CreateProject {
8+
name,
9+
decl: requests::ProjectDecl {
10+
url,
11+
flake: flake.is_some()
12+
},
13+
},
14+
|result| {
15+
// TODO: redirect when success
16+
// if let Ok(Ok(())) = result {
17+
// leptos_actix::redirect("/");
18+
// }
19+
result
20+
}
21+
);
22+
let value: RwSignal<Option<_>> = action.value();
23+
let response = move || {
24+
value().map(|v| match v {
25+
Ok(Err(ResponseError::BadRequest(message))) => Err(message),
26+
Ok(_) => Ok(()),
27+
Err(e) => Err(format!("Fatal error: {e:?}")),
28+
})
29+
};
30+
let style = style! {
31+
.header {
32+
font-size: var(--font-size-huge);
33+
padding-bottom: 2px;
34+
}
35+
.header-description {
36+
color: var(--color-fg-muted);
37+
padding-top: 4px;
38+
margin-bottom: 14px;
39+
}
40+
.sep {
41+
border-bottom: 1px solid var(--color-border-default);
42+
padding-bottom: 14px;
43+
}
44+
.page {
45+
padding-top: 22px;
46+
max-width: 600px;
47+
margin: auto;
48+
}
49+
.fields {
50+
display: flex;
51+
flex-direction: column;
52+
gap: 14px;
53+
}
54+
.field > label {
55+
display: block;
56+
padding-left: 2px;
57+
font-size: var(--font-size-small);
58+
font-weight: 400;
59+
}
60+
.field > input {
61+
width: 100%;
62+
}
63+
.field.flake {
64+
display: flex;
65+
flex-direction: column;
66+
gap: 16px;
67+
}
68+
.field.flake .option {
69+
display: grid;
70+
grid-template-areas: raw_str("radio icon title") raw_str("radio icon details");
71+
grid-template-columns: auto auto 1fr;
72+
align-items: center;
73+
column-gap: 5px;
74+
}
75+
.field.flake .option input {
76+
grid-area: radio;
77+
}
78+
.field.flake :deep(svg) {
79+
grid-area: icon;
80+
font-size: 20px;
81+
}
82+
.field.flake .option .kind {
83+
font-size: var(--font-size-small);
84+
font-weight: 400;
85+
grid-area: title;
86+
padding-bottom: 2px;
87+
}
88+
.field.flake .option .details {
89+
font-size: var(--font-size-small);
90+
grid-area: details;
91+
color: var(--color-fg-muted);
92+
padding-top: 2px;
93+
}
94+
button {
95+
background: var(--color-green-button-bg);
96+
color: white;
97+
width: auto!important;
98+
transition: all 100ms;
99+
}
100+
button:hover {
101+
transition: all 100ms;
102+
background: var(--color-green);
103+
}
104+
button:active {
105+
transition: all 100ms;
106+
background: var(--color-darkgreen);
107+
}
108+
.page :deep(.message) {
109+
text-align: justify;
110+
padding-bottom: 14px;
111+
display: flex;
112+
align-items: center;
113+
gap: 6px;
114+
}
115+
.page :deep(.error-message) {
116+
color: var(--color-danger-emphasis);
117+
}
118+
.page :deep(.success-message) {
119+
color: var(--color-success);
120+
}
121+
// TODO: Move to `app.rs`
122+
.page :deep(button) {
123+
border-radius: 6px;
124+
border: 1px solid var(--color-border-default);
125+
padding: 8px 10px;
126+
cursor: pointer;
127+
outline: inherit;
128+
font-size: inherit;
129+
font-family: inherit;
130+
font-weight: 400;
131+
}
132+
};
133+
view! { class=style,
134+
<div class="page">
135+
<ActionForm action>
136+
<div class="header">"Add a project"</div>
137+
<div class="header-description sep">
138+
"Add continuous integration with Typhon for an existing codebase. This project will be visible to anyone that have access to this Typhon instance."
139+
</div>
140+
<div class="fields">
141+
<div class="field">
142+
<label class="label" for="name">
143+
"Name"
144+
</label>
145+
<input type="text" name="name" class="input" id="name"/>
146+
</div>
147+
<div class="field sep">
148+
<label class="label" for="url">
149+
"Flake URL"
150+
</label>
151+
<input type="text" name="url" class="input" id="url"/>
152+
</div>
153+
<div class="field sep flake">
154+
155+
<div class="option">
156+
<input name="flake" class="input" id="flake" type="radio" checked=true/>
157+
<Icon icon=icondata::FaSnowflakeRegular/>
158+
<label class="kind" for="flake">
159+
"Flake"
160+
</label>
161+
<label class="details">
162+
The project has a <code>flake.nix</code> <span>.</span>
163+
The project is a
164+
<a href="https://nixos.wiki/wiki/Flakes">Nix Flake</a>
165+
<span>.</span>
166+
</label>
167+
</div>
168+
169+
<div class="option">
170+
<input name="flake" class="input" id="legacy" type="radio"/>
171+
<Icon icon=icondata::BiCodeCurlyRegular/>
172+
<label class="kind" for="legacy">
173+
"Legacy"
174+
</label>
175+
<label class="details" for="legacy">
176+
The project has a
177+
<code>default.nix</code>
178+
<span>.</span>
179+
</label>
180+
</div>
181+
182+
</div>
183+
<div class="field">
184+
{move || {
185+
match response() {
186+
Some(Err(error)) => {
187+
view! {
188+
<div class="message error-message">
189+
<Icon icon=icondata::BiErrorSolid/>
190+
<div>{error}</div>
191+
</div>
192+
}
193+
.into_view()
194+
}
195+
Some(Ok(())) => {
196+
view! {
197+
<div class="message success-message">
198+
<Icon icon=icondata::BiCheckCircleSolid/>
199+
<div>"The project have been created successfully."</div>
200+
</div>
201+
}
202+
.into_view()
203+
}
204+
None => ().into_view(),
205+
}
206+
}}
207+
<button type="submit" style="float: right;">
208+
"Add project"
209+
</button>
210+
</div>
211+
</div>
212+
</ActionForm>
213+
</div>
214+
}
215+
}

typhon-webapp/src/pages/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod add_project;
12
pub mod dashboard;
23
pub mod error;
34
pub mod evaluation;
@@ -6,6 +7,7 @@ pub mod login;
67
pub mod project;
78
pub mod projects;
89

10+
pub(crate) use add_project::AddProject;
911
pub(crate) use dashboard::Dashboard;
1012
pub(crate) use error::*;
1113
pub(crate) use evaluation::Evaluation;

0 commit comments

Comments
 (0)