9 Commits
0.1.2 ... login

Author SHA1 Message Date
Tuan-Dat Tran
b44792871c Added basic layout for login
Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
2024-09-18 14:03:01 +02:00
Tuan-Dat Tran
ac80065e82 Fixed multi component i18n strings and added serverside impressum
Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
2024-09-08 22:53:48 +02:00
Tuan-Dat Tran
3610f338aa centralize header
Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
2024-09-03 12:40:48 +02:00
Tuan-Dat Tran
bec4c608f1 Fixed flex-boxes in CV section
Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
2024-09-03 11:23:00 +02:00
Tuan-Dat Tran
f76a7a8c4c Added Social and tweaked Home
Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
2024-09-02 18:18:08 +02:00
Tuan-Dat Tran
0ff5419c23 Added some SEO and ran cargo update
Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
2024-08-30 18:11:23 +02:00
Tuan-Dat Tran
b7a47ca903 Fixed robots.txt and enabled more caching for docker
Some checks reported warnings
Build Docker Image / Build (push) Has been cancelled
Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
2024-07-29 00:25:56 +02:00
Tuan-Dat Tran
b3eb962024 Updated images
Some checks reported warnings
Build Docker Image / Build (push) Has been cancelled
Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
2024-07-28 18:39:21 +02:00
d9f904231a Merge pull request '0.1.2' (#11) from 0.1.2 into dev
Reviewed-on: #11
2024-07-25 12:10:22 +02:00
32 changed files with 1014 additions and 962 deletions

1223
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,25 @@
[package]
name = "athome"
version = "0.2.1"
version = "0.2.2"
authors = ["Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus = { version = "0.5", features = ["fullstack", "router"] }
dioxus-sdk = { version = "0.5.0", features = ["i18n"] }
dioxus-logger = "0.5.0"
dioxus-free-icons = { version = "0.8", features = ["font-awesome-brands"] }
tokio = { version = "1", features = ["full"], optional = true }
tracing = "0.1.40"
serde = { version = "1.0.197", features = ["derive"] }
dioxus = { version = "0.5", features = ["fullstack", "router"] }
# Debug
tracing = "0.1.40"
dioxus-logger = "0.5.0"
manganis = "0.2.2"
dioxus-free-icons = { version = "0.8", features = ["font-awesome-brands"] }
dioxus-sdk = { version = "0.5.0", features = ["i18n"] }
lazy_static = "1.4.0"
manganis = "0.2.2"
simple_logger = { version = "4.2.0", optional = true }
axum-login = "0.16.0"
[features]
default = []
server = ["dioxus/axum"]
server = ["dioxus/fullstack"]
web = ["dioxus/web"]

View File

@@ -1,4 +1,4 @@
FROM rust:1.79.0 AS dioxus
FROM rust:1.80.1 AS dioxus
RUN cargo install dioxus-cli@^0.5
FROM dioxus AS builder
@@ -11,7 +11,8 @@ COPY ./Cargo.toml ./Cargo.toml
COPY ./input.css ./input.css
COPY ./Dioxus.toml ./Dioxus.toml
COPY ./tailwind.config.js ./tailwind.config.js
RUN npx tailwindcss -i ./input.css -o ./assets/tailwind.css && dx build --platform fullstack --release
RUN npx tailwindcss -i ./input.css -o ./assets/tailwind.css
RUN dx build --platform fullstack --release
FROM dioxus AS runner
WORKDIR /app/

View File

@@ -2,6 +2,16 @@
My personal website.
## Usage
```sh
npx tailwindcss -i ./input.css -o ./assets/tailwind.css --watch
```
```sh
dx serve --platform fullstack
```
## Screenshot
[[./resources/screenshot.webp]]

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
assets/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
assets/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

BIN
assets/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1 @@
<svg version="1.1" id="main_outline" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" style="enable-background:new 0 0 640 640;" xml:space="preserve" viewBox="5.67 143.05 628.65 387.55"> <g> <path id="teabag" style="fill:#FFFFFF" d="M395.9,484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5,21.2-17.9,33.8-11.8 c17.2,8.3,27.1,13,27.1,13l-0.1-109.2l16.7-0.1l0.1,117.1c0,0,57.4,24.2,83.1,40.1c3.7,2.3,10.2,6.8,12.9,14.4 c2.1,6.1,2,13.1-1,19.3l-61,126.9C423.6,484.9,408.4,490.3,395.9,484.2z"></path> <g> <g> <path style="fill:#609926" d="M622.7,149.8c-4.1-4.1-9.6-4-9.6-4s-117.2,6.6-177.9,8c-13.3,0.3-26.5,0.6-39.6,0.7c0,39.1,0,78.2,0,117.2 c-5.5-2.6-11.1-5.3-16.6-7.9c0-36.4-0.1-109.2-0.1-109.2c-29,0.4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5 c-9.8-0.6-22.5-2.1-39,1.5c-8.7,1.8-33.5,7.4-53.8,26.9C-4.9,212.4,6.6,276.2,8,285.8c1.7,11.7,6.9,44.2,31.7,72.5 c45.8,56.1,144.4,54.8,144.4,54.8s12.1,28.9,30.6,55.5c25,33.1,50.7,58.9,75.7,62c63,0,188.9-0.1,188.9-0.1s12,0.1,28.3-10.3 c14-8.5,26.5-23.4,26.5-23.4s12.9-13.8,30.9-45.3c5.5-9.7,10.1-19.1,14.1-28c0,0,55.2-117.1,55.2-231.1 C633.2,157.9,624.7,151.8,622.7,149.8z M125.6,353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6,321.8,60,295.4 c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5,38.5-30c13.8-3.7,31-3.1,31-3.1s7.1,59.4,15.7,94.2c7.2,29.2,24.8,77.7,24.8,77.7 S142.5,359.9,125.6,353.9z M425.9,461.5c0,0-6.1,14.5-19.6,15.4c-5.8,0.4-10.3-1.2-10.3-1.2s-0.3-0.1-5.3-2.1l-112.9-55 c0,0-10.9-5.7-12.8-15.6c-2.2-8.1,2.7-18.1,2.7-18.1L322,273c0,0,4.8-9.7,12.2-13c0.6-0.3,2.3-1,4.5-1.5c8.1-2.1,18,2.8,18,2.8 l110.7,53.7c0,0,12.6,5.7,15.3,16.2c1.9,7.4-0.5,14-1.8,17.2C474.6,363.8,425.9,461.5,425.9,461.5z"></path> <path style="fill:#609926" d="M326.8,380.1c-8.2,0.1-15.4,5.8-17.3,13.8c-1.9,8,2,16.3,9.1,20c7.7,4,17.5,1.8,22.7-5.4 c5.1-7.1,4.3-16.9-1.8-23.1l24-49.1c1.5,0.1,3.7,0.2,6.2-0.5c4.1-0.9,7.1-3.6,7.1-3.6c4.2,1.8,8.6,3.8,13.2,6.1 c4.8,2.4,9.3,4.9,13.4,7.3c0.9,0.5,1.8,1.1,2.8,1.9c1.6,1.3,3.4,3.1,4.7,5.5c1.9,5.5-1.9,14.9-1.9,14.9 c-2.3,7.6-18.4,40.6-18.4,40.6c-8.1-0.2-15.3,5-17.7,12.5c-2.6,8.1,1.1,17.3,8.9,21.3c7.8,4,17.4,1.7,22.5-5.3 c5-6.8,4.6-16.3-1.1-22.6c1.9-3.7,3.7-7.4,5.6-11.3c5-10.4,13.5-30.4,13.5-30.4c0.9-1.7,5.7-10.3,2.7-21.3 c-2.5-11.4-12.6-16.7-12.6-16.7c-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3c4.7-9.7,9.4-19.3,14.1-29 c-4.1-2-8.1-4-12.2-6.1c-4.8,9.8-9.7,19.7-14.5,29.5c-6.7-0.1-12.9,3.5-16.1,9.4c-3.4,6.3-2.7,14.1,1.9,19.8 C343.2,346.5,335,363.3,326.8,380.1z"></path> </g> </g> </g> </svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg id="Group_1282" data-name="Group 1282" xmlns="http://www.w3.org/2000/svg" width="76.624" height="65.326" viewBox="0 0 76.624 65.326">
<path id="Path_2525" data-name="Path 2525" d="M1165,274.515a1.2,1.2,0,0,0,1.21-1.269c0-.9-.543-1.33-1.657-1.33h-1.8v4.712h.677v-2.054h.832l.019.025,1.291,2.029h.724l-1.389-2.1Zm-.783-.472h-.785V272.45h.995c.514,0,1.1.084,1.1.757,0,.774-.593.836-1.314.836" transform="translate(-1092.136 -213.406)" fill="#0a66c2"/>
<path id="Path_2520" data-name="Path 2520" d="M958.98,112.559h-9.6V97.525c0-3.585-.064-8.2-4.993-8.2-5,0-5.765,3.906-5.765,7.939v15.294h-9.6V81.642h9.216v4.225h.129a10.1,10.1,0,0,1,9.093-4.994c9.73,0,11.524,6.4,11.524,14.726ZM918.19,77.416a5.571,5.571,0,1,1,5.57-5.572,5.571,5.571,0,0,1-5.57,5.572m4.8,35.143h-9.61V81.642h9.61Zm40.776-55.2h-55.21a4.728,4.728,0,0,0-4.781,4.67v55.439a4.731,4.731,0,0,0,4.781,4.675h55.21a4.741,4.741,0,0,0,4.8-4.675V62.025a4.738,4.738,0,0,0-4.8-4.67" transform="translate(-903.776 -57.355)" fill="#0a66c2"/>
<path id="Path_2526" data-name="Path 2526" d="M1156.525,264.22a4.418,4.418,0,1,0,.085,0h-.085m0,8.33a3.874,3.874,0,1,1,3.809-3.938c0,.022,0,.043,0,.065a3.791,3.791,0,0,1-3.708,3.871h-.1" transform="translate(-1084.362 -207.809)" fill="#0a66c2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

View File

@@ -32,7 +32,7 @@ User-Agent: GPTBot
Disallow: /
User-agent: *
Disallow: /*
Disallow: /impressum
Allow: /
Allow: /resume
Allow: /publications

1
assets/site.webmanifest Normal file
View File

@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

View File

@@ -608,21 +608,11 @@ video {
inset-inline-start: -0.375rem;
}
.mx-16 {
margin-left: 4rem;
margin-right: 4rem;
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}
.my-4 {
margin-top: 1rem;
margin-bottom: 1rem;
}
.my-8 {
margin-top: 2rem;
margin-bottom: 2rem;
@@ -676,15 +666,6 @@ video {
display: flex;
}
.size-auto {
width: auto;
height: auto;
}
.h-16 {
height: 4rem;
}
.h-24 {
height: 6rem;
}
@@ -709,12 +690,28 @@ video {
height: 100vh;
}
.min-h-screen {
min-height: 100vh;
.h-5\/6 {
height: 83.333333%;
}
.w-16 {
width: 4rem;
.h-1\/6 {
height: 16.666667%;
}
.h-2\/3 {
height: 66.666667%;
}
.h-4\/6 {
height: 66.666667%;
}
.h-full {
height: 100%;
}
.min-h-screen {
min-height: 100vh;
}
.w-24 {
@@ -729,6 +726,10 @@ video {
width: 100%;
}
.w-screen {
width: 100vw;
}
.min-w-fit {
min-width: -moz-fit-content;
min-width: fit-content;
@@ -770,6 +771,10 @@ video {
flex-wrap: wrap;
}
.content-center {
align-content: center;
}
.items-center {
align-items: center;
}
@@ -786,14 +791,18 @@ video {
justify-content: space-between;
}
.gap-2 {
gap: 0.5rem;
}
.gap-4 {
gap: 1rem;
}
.space-x-8 > :not([hidden]) ~ :not([hidden]) {
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(2rem * var(--tw-space-x-reverse));
margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse)));
margin-right: calc(1rem * var(--tw-space-x-reverse));
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-y-2 > :not([hidden]) ~ :not([hidden]) {
@@ -802,8 +811,10 @@ video {
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
}
.overflow-x-auto {
overflow-x: auto;
.space-y-8 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(2rem * var(--tw-space-y-reverse));
}
.rounded {
@@ -822,6 +833,10 @@ video {
border-radius: 0.375rem;
}
.rounded-xl {
border-radius: 0.75rem;
}
.rounded-e-lg {
border-start-end-radius: 0.5rem;
border-end-end-radius: 0.5rem;
@@ -832,6 +847,11 @@ video {
border-end-start-radius: 0.5rem;
}
.rounded-l {
border-top-left-radius: 0.25rem;
border-bottom-left-radius: 0.25rem;
}
.border {
border-width: 1px;
}
@@ -848,6 +868,10 @@ video {
border-inline-start-width: 1px;
}
.border-none {
border-style: none;
}
.border-gray-200 {
--tw-border-opacity: 1;
border-color: rgb(229 231 235 / var(--tw-border-opacity));
@@ -913,6 +937,21 @@ video {
background-color: rgb(254 249 195 / var(--tw-bg-opacity));
}
.bg-gray-900 {
--tw-bg-opacity: 1;
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
}
.bg-gray-700 {
--tw-bg-opacity: 1;
background-color: rgb(55 65 81 / var(--tw-bg-opacity));
}
.bg-gray-800 {
--tw-bg-opacity: 1;
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
}
.bg-gradient-to-br {
background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
}
@@ -939,6 +978,12 @@ video {
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.from-blue-600 {
--tw-gradient-from: #2563eb var(--tw-gradient-from-position);
--tw-gradient-to: rgb(37 99 235 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.to-blue-500 {
--tw-gradient-to: #3b82f6 var(--tw-gradient-to-position);
}
@@ -951,15 +996,15 @@ video {
--tw-gradient-to: #059669 var(--tw-gradient-to-position);
}
.to-cyan-300 {
--tw-gradient-to: #67e8f9 var(--tw-gradient-to-position);
}
.bg-clip-text {
-webkit-background-clip: text;
background-clip: text;
}
.p-3 {
padding: 0.75rem;
}
.p-4 {
padding: 1rem;
}
@@ -1018,10 +1063,28 @@ video {
padding-bottom: 1rem;
}
.px-10 {
padding-left: 2.5rem;
padding-right: 2.5rem;
}
.py-8 {
padding-top: 2rem;
padding-bottom: 2rem;
}
.pb-4 {
padding-bottom: 1rem;
}
.pl-2 {
padding-left: 0.5rem;
}
.pb-2 {
padding-bottom: 0.5rem;
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
@@ -1166,6 +1229,11 @@ video {
color: rgb(133 77 14 / var(--tw-text-opacity));
}
.text-black {
--tw-text-opacity: 1;
color: rgb(0 0 0 / var(--tw-text-opacity));
}
.shadow {
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
@@ -1184,6 +1252,17 @@ video {
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow-xl {
--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.outline-none {
outline: 2px solid transparent;
outline-offset: 2px;
}
.grayscale {
--tw-grayscale: grayscale(100%);
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
@@ -1286,6 +1365,12 @@ video {
flex-direction: row;
}
.sm\:space-x-8 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(2rem * var(--tw-space-x-reverse));
margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse)));
}
.sm\:space-y-0 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));

View File

@@ -21,6 +21,19 @@ pub fn P(props: PProps) -> Element {
}
}
#[component]
pub fn Title(props: PProps) -> Element {
rsx! {
div {
class: "{props.class}",
h1 {
class: "mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white",
{props.children}
}
}
}
}
#[component]
pub fn H1(props: PProps) -> Element {
rsx! {
@@ -84,7 +97,7 @@ pub fn Card(prop: CardProp) -> Element {
Picture {src: "{prop.picture}"},
}
}
H5 { "{prop.name}", span { class: "text-grey-600 dark:text-grey-500 text-lg", " {prop.gender}" } },
Title { "{prop.name}", span { class: "text-grey-600 dark:text-grey-500 text-lg", " {prop.gender}" } },
{ prop.children }
}
}
@@ -129,3 +142,105 @@ pub fn HR() -> Element {
hr { class:"h-px my-8 bg-gray-200 border-0 dark:bg-gray-700"}
}
}
#[derive(Clone, PartialEq, Props)]
pub struct BoldingProp {
authors: String,
patterns: Vec<String>,
}
pub fn Bolding(prop: BoldingProp) -> Element {
let mut elements = vec![];
let mut last_index = 0;
let authors_text = &prop.authors;
let mut matches: Vec<(usize, usize, &str)> = vec![];
for pattern in &prop.patterns {
for (start, _) in authors_text.match_indices(pattern) {
matches.push((start, start + pattern.len(), pattern));
}
}
matches.sort_by_key(|(start, _, _)| *start);
for (start, end, matched_pattern) in matches {
if last_index < start {
elements.push(rsx! { "{&authors_text[last_index..start]}" });
}
elements.push(rsx! { b { "{matched_pattern}" } });
last_index = end;
}
if last_index < authors_text.len() {
elements.push(rsx! { "{&authors_text[last_index..]}" });
}
rsx! {
div {
P {
for i in elements {
{i}
}
}
}
}
}
#[derive(Clone, PartialEq, Props)]
pub struct UrlingProp {
#[props(default = "".to_string())]
class: String,
text: String,
patterns: Vec<String>,
url: String,
#[props(default = true)]
new_tab: bool,
}
pub fn Urling(prop: UrlingProp) -> Element {
let mut elements = vec![];
let mut last_index = 0;
let text = &prop.text;
let mut matches: Vec<(usize, usize, &str)> = vec![];
for pattern in &prop.patterns {
for (start, _) in text.match_indices(pattern) {
matches.push((start, start + pattern.len(), pattern));
}
}
matches.sort_by_key(|(start, _, _)| *start);
for (start, end, matched_pattern) in matches {
if last_index < start {
elements.push(rsx! { "{&text[last_index..start]}" });
}
elements.push(rsx! {
Link {
class: "{prop.class}",
to: "{prop.url}",
new_tab: prop.new_tab,
"{matched_pattern}"
}
});
last_index = end;
}
if last_index < text.len() {
elements.push(rsx! { "{&text[last_index..]}" });
}
rsx! {
div {
P {
for i in elements {
{i}
}
}
}
}
}

View File

@@ -1,10 +0,0 @@
use dioxus::prelude::*;
use crate::components::UnderConstruction;
#[component]
pub fn Consulting() -> Element {
rsx! {
UnderConstruction { },
}
}

View File

@@ -7,16 +7,25 @@ use crate::components::{H4, HR};
pub fn CV() -> Element {
rsx! {
div {
class: "flex flex-col ",
Introduction {},
class: "flex flex-col",
div {
class: "flex flex-col sm:flex-row justify-center items-center sm:space-x-8 space-y-8 sm:space-y-0",
img {
class: "rounded-full w-24 h-24",
alt: "headshot",
src: "/pictures/headshot.webp"
}
Introduction {},
Socials {}
},
HR {}
div {
class: "flex justify-between",
class: "flex flex-col justify-between sm:flex-row",
WorkExperience {},
Miscellaneous {},
},
HR {},
Socials {}
}
}
}
@@ -27,12 +36,9 @@ fn Introduction() -> Element {
rsx! {
div {
class: "flex",
img {
class: "rounded-full w-16 h-16 mx-16",
src: "/pictures/headshot.webp"
}
P { { translate!(i18, "cv.introduction") }}
}
P { { translate!(i18, "cv.introduction_0") } },
P { { translate!(i18, "cv.introduction_1") } }
},
}
}
@@ -189,7 +195,7 @@ fn CVEntry(props: CVEntryProps) -> Element {
time { class:"mb-1 text-sm font-normal leading-none text-gray-400 dark:text-gray-500", "{props.time}"},
h6 { class: "text-lg font-semibold text-gray-900 dark:text-white", "{props.title}"}
ul {
class: "flex",
class: "flex flex-wrap gap-2",
for (index, value) in props.technologies.iter().enumerate() {
li { key: "{index}", RandomBadge { text: "{value}"} }
}
@@ -228,9 +234,17 @@ fn random_badge_color(seed: usize) -> String {
}
fn Socials() -> Element {
let i18 = use_i18();
rsx! {
div {
"todo!()"
class: "ms-8 max-w-3/4",
H4 { { translate!(i18, "cv.socials.title") } },
div {
class: "flex justify-center items-center space-x-4",
P { Link { to:"https://www.linkedin.com/in/tudattr/", class:"hover:underline", new_tab: true, img { class: "h-8", src:"/pictures/LI-Bug.svg.original.svg", alt:"LinkedIn Logo" } }},
P { Link { to:"https://git.tudattr.dev/tudattr", class:"hover:underline", new_tab: true, img { class: "h-8", src:"/pictures/Gitea_Logo.svg", alt:"Gitea Logo" } }},
}
}
}
}

View File

@@ -1,10 +1,11 @@
use crate::components::{Card, P};
use crate::components::{Card, Urling, P};
use dioxus::prelude::*;
use dioxus_sdk::{i18n::use_i18, translate};
#[component]
pub fn Home() -> Element {
let i18 = use_i18();
rsx! {
div {
class: "container mx-auto p-4 flex items-center justify-center max-w-md w-full",
@@ -16,16 +17,16 @@ pub fn Home() -> Element {
class: "py-4",
div {
class: "mb-2",
P { { translate!(i18, "home.card.l1") } },
P { { translate!(i18, "home.card.l2") } },
P { { translate!(i18, "home.card.l3") },
Link {
to: "https://git.tudattr.dev/explore/repos",
new_tab: true,
class: "items-center font-medium hover:underline",
"gitea"},
{ translate!(i18, "home.card.l3_1") }
},
for line in translate!(i18, "home.card.text").split("\n").into_iter() {
P {
Urling {
class: "items-center font-medium hover:underline",
text: line,
patterns: vec!["Gitea".to_string()],
url: "https://git.tudattr.dev/explore/repos"
}
}
}
},
},
Link {

View File

@@ -1,48 +1,60 @@
use dioxus::prelude::*;
use dioxus_sdk::{i18n::use_i18, translate};
use tracing::info;
use crate::components::{H1, HR, P};
#[component]
pub fn Impressum() -> Element {
let mut show_impressum = use_signal(|| false);
let i18 = use_i18();
let mut impressum = use_signal(Vec::<String>::new);
let mut contact = use_signal(Vec::<String>::new);
rsx! {
if show_impressum() {
div {
div {
div {
class: "flex flex-col items-center",
button {
onclick: move |_| {
info!("Hide impressum.");
},
H1 { { translate!(i18, "impressum.on") } },
},
P { "Tuan-Dat Tran" },
P { "c/o AutorenServices.de" },
P { "Birkenallee 24" },
P { "36037 Fulda" },
},
HR {}
div {
class: "flex flex-col items-center",
P { "tuan-dat.tran@tudattr.dev" },
P { "+49 176 83468388" },
}
}
} else {
div {
class: "flex flex-col items-center p-3",
class: "flex flex-col items-center",
button {
onclick: move |_| async move {
show_impressum.set(true);
if let Ok(data) = get_impressum().await {
impressum.set(data.clone());
}
if let Ok(data) = get_contact().await {
contact.set(data.clone());
}
},
H1 { { translate!(i18, "impressum.off") } },
H1 { { translate!(i18, "impressum.on") } },
},
},
div {
class: "flex flex-col items-center",
for line in impressum() {
P { {line} }
}
}
if !impressum.read().is_empty() { HR{} },
div {
class: "flex flex-col items-center",
for line in contact() {
P { {line} }
}
}
}
}
}
#[server(GetServerData)]
async fn get_impressum() -> Result<Vec<String>, ServerFnError> {
Ok(vec![
"Tuan-Dat Tran".to_string(),
"c/o AutorenServices.de".to_string(),
"Birkenallee 24".to_string(),
"36037 Fulda".to_string(),
])
}
async fn get_contact() -> Result<Vec<String>, ServerFnError> {
Ok(vec![
"tuan-dat.tran@tudattr.dev".to_string(),
"+49 176 83468388".to_string(),
])
}

View File

@@ -16,15 +16,12 @@
"card": {
"name": "Tuan-Dat Tran",
"gender": "",
"l1": "Hallihallo! 👋🏻👋🏼👋🏽👋🏾👋🏿",
"l2": "Willkommen auf meiner kleinen Webseite im World Wide Web.",
"l3": "Während du hier bist, schau dir doch meine Projekte auf ",
"l3_1": " an.",
"text": "Hallihallo! 👋🏻👋🏼👋🏽👋🏾👋🏿\nWillkommen auf meiner kleinen Webseite im World Wide Web.\nMein Name ist Tuan und ich bin Linux-Bastler, IT-Security Nerd und Automatisierer aus Leidenschaft.\nWährend du hier bist, schau dir doch meine Projekte auf Gitea an.\n",
"contact_button": "Get in touch."
}
},
"cv": {
"introduction": "Während meines Bachelorstudiums habe ich viele Erfahrungen in der Industrie und im Studium gesammelt. Meine beruflichen und persönlichen Interessen sind DevOps/IaC, Systems/Software Security und Computer Networking. All diese Interessen vertiefe ich in persönlichen Projekten wie meinem Homelab und CTF-Challenges.",
"introduction_0": "Während meines Bachelorstudiums habe ich viele Erfahrungen in der Industrie und im Studium gesammelt. Meine beruflichen und persönlichen Interessen sind DevOps/IaC, Systems/Software Security und Computer Networking. All diese Interessen vertiefe ich in persönlichen Projekten wie meinem Homelab und CTF-Challenges.",
"workexperience": {
"title": "Berufserfahrung",
"se1_gefeba": {
@@ -53,6 +50,9 @@
"description": "Während meiner Tätigkeit als wissenschaftlicher Mitarbeiter in der Network Communication System Research Group an der Universität Duisburg-Essen habe ich an der Forschung rund um Software Defined Networking, 5G, Staukontrollalgorithmen und föderiertes maschinelles Lernen mitgearbeitet. Ich habe die On-Premise- und Cloud-Infrastruktur, das Inventarsystem und die Online-Präsenz der Forschungsgruppe aufgebaut und verwaltet."
}
},
"socials": {
"title": "Profile"
},
"education": {
"title": "Bildungsweg",
"bachelor": {
@@ -142,10 +142,13 @@
"under_construction": "Diese Seite befindet sich gerade im Aufbau"
},
"footer": {
"year": "© 2024 ",
"year": " 2024 ",
"name": "Tuan-Dat Tran",
"rights": ". All Rights Reserved.",
"contact": "Kontakt"
},
"login": {
"title": "Login"
}
}
}

View File

@@ -3,7 +3,7 @@
"texts": {
"headers": {
"home": "Home",
"cv": "CV",
"cv": "Résumé",
"publications_projects": "Publications/Projects",
"consulting": "Consulting",
"about": "About",
@@ -16,15 +16,12 @@
"card": {
"name": "Tuan-Dat Tran",
"gender": "(He/Him)",
"l1": "Hey there! 👋🏻👋🏼👋🏽👋🏾👋🏿",
"l2": "Welcome to my little place on the internet.",
"l3": "While you're here, why don't you check out my projects over on ",
"l3_1": "?",
"text": "Hey there! 👋🏻👋🏼👋🏽👋🏾👋🏿\nWelcome to my little place on the internet\nMy name is Tuan and I'm passionate about Linux, system security, automation and all things tech.\nWhile you're here, why don't you check out my projects over on Gitea?",
"contact_button": "Get in touch."
}
},
"cv": {
"introduction": "While studying for my bachelors degree I accumulated a lot of industry and academic experience. My professional and personal intererests are DevOps/IaC, Systems/Software Security and Computer Networking. All of which I deepen in personal projects such as my homelab and CTF challenges.",
"introduction_0": "While studying for my bachelors degree I accumulated a lot of industry and academic experience. My professional and personal intererests are DevOps/IaC, Systems/Software Security and Computer Networking. All of which I deepen in personal projects such as my homelab and CTF challenges.",
"workexperience": {
"title": "Work Experience",
"se1_gefeba": {
@@ -53,6 +50,9 @@
"description": "While working for the Network Communication System Research Group at the University Duisburg-Essen as a research assistant I've assisted in research around software defined networking, 5G, congestion control algorithms and federated machine learning. I've established and managed the research groups on-premise and cloud infractructure, inventory system and online presence."
}
},
"socials": {
"title": "Socials"
},
"education": {
"title": "Education",
"bachelor": {
@@ -142,10 +142,13 @@
"under_construction": "This page is currently under construction"
},
"footer": {
"year": "© 2024 ",
"year": " 2024 ",
"name": "Tuan-Dat Tran",
"rights": ". All Rights Reserved.",
"contact": "Contact"
},
"login": {
"title": "Login"
}
}
}

View File

@@ -1,30 +1,29 @@
use dioxus::prelude::*;
use dioxus_sdk::{i18n::use_i18, translate};
use crate::components::H1;
use crate::Route;
pub fn Footer() -> Element {
let i18 = use_i18();
rsx! {
div {
class: "container mx-auto",
// ToolsUsed {},
class: "container mx-auto pb-4",
footer {
class:"bg-white rounded-lg shadow dark:bg-gray-800",
div {
class:"w-full mx-auto p-4 flex items-center justify-between",
span {
class:"text-sm text-gray-500 sm:text-center dark:text-gray-400",
Link {to: Route::Home {}, "©"},
{ translate!(i18, "footer.year") },
a { href: "#", class: "hover:underline", { translate!(i18, "footer.name") }},
Link { class: "hover:underline", to: "#", { translate!(i18, "footer.name") }},
{ translate!(i18, "footer.rights") }
}
ul {
class:"flex flex-wrap items-center mt-3 text-sm font-medium text-gray-500 dark:text-gray-400 sm:mt-0",
li {
Link { to:"mailto:tuan-dat.tran@tudattr.dev", class:"hover:underline", { translate!(i18, "footer.contact") } }
},
},
Link {
class:"text-sm text-gray-500 sm:text-center dark:text-gray-400 hover:underline",
to:"mailto:tuan-dat.tran@tudattr.dev",
{ translate!(i18, "footer.contact") }
}
}
}
@@ -32,42 +31,6 @@ pub fn Footer() -> Element {
}
}
#[allow(dead_code)]
fn ToolsUsed() -> Element {
rsx! {
div {
class:"items-center bg-white rounded-lg shadow dark:bg-gray-800 p-4 my-4",
div {
H1 {class: "justify-center", "Tools used" },
}
div {
class: "flex h-fill overflow-x-auto",
Logo {
src: "https://raw.githubusercontent.com/SAWARATSUKI/Logogs/main/Rust/Rust.png",
alt: "Rust"
},
Logo {
src: "https://raw.githubusercontent.com/SAWARATSUKI/Logogs/main/Tailwindcss/Tailwindcss6.png",
alt: "Tailwindcss"
},
Logo {
src: "https://raw.githubusercontent.com/SAWARATSUKI/Logogs/main/Html/HTML.png",
alt: "HTML"
},
Logo {
src: "https://raw.githubusercontent.com/Aikoyori/ProgrammingVTuberLogos/main/Docker/DockerLogo.png",
alt: "Docker"
},
Logo {
src: "https://raw.githubusercontent.com/Aikoyori/ProgrammingVTuberLogos/main/Neovim/NeovimLogo.png",
alt: "NeoVim"
},
}
}
}
}
#[component]
fn Logo(src: String, alt: String) -> Element {
rsx! {

View File

@@ -9,11 +9,10 @@ pub fn Header() -> Element {
rsx! {
nav {
div {
// class: "justify-between p-4 space-x-8",
class: "container mx-auto p-4",
class: "container mx-auto py-4",
ul {
class:"flex flex-col justify-between sm:flex-row justify-center space-y-2 sm:space-y-0 sm:space-s-4",
class:"flex flex-col justify-between items-center justify-center space-y-2 sm:flex-row sm:space-y-0 sm:space-s-4",
li {
Link {
to: Route::Home {},
@@ -24,7 +23,6 @@ pub fn Header() -> Element {
li { HeaderLink { url: Route::Home {}, text: translate!(i18, "headers.home")} },
li { HeaderLink { url: Route::CV {}, text: translate!(i18, "headers.cv") } },
li { HeaderLink { url: Route::PublicationsProjects {}, text: translate!(i18, "headers.publications_projects") } },
li { HeaderLink { url: Route::Consulting {}, text: translate!(i18, "headers.consulting") } },
li { HeaderLink { url: Route::Impressum {}, text: translate!(i18, "headers.about") } },
li { LanguageButtonGroup {} },
},
@@ -42,9 +40,15 @@ fn LanguageButtonGroup() -> Element {
rsx! {
div {
class: "rounded-md shadow-sm justify-end",
button { class: "px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-s-lg hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-2 focus:ring-blue-700 focus:text-blue-700 dark:bg-gray-800 dark:border-gray-700 dark:text-white dark:hover:text-white dark:hover:bg-gray-700 dark:focus:ring-blue-500 dark:focus:text-white", onclick: change_to_english, label { { translate!(i18, "headers.language_buttons.english") } } },
button { class: "px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-e-lg hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-2 focus:ring-blue-700 focus:text-blue-700 dark:bg-gray-800 dark:border-gray-700 dark:text-white dark:hover:text-white dark:hover:bg-gray-700 dark:focus:ring-blue-500 dark:focus:text-white", onclick: change_to_german, label { { translate!(i18, "headers.language_buttons.german") } } }
class: "rounded-md shadow-sm justify-end",
button {
class: "px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-s-lg hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-2 focus:ring-blue-700 focus:text-blue-700 dark:bg-gray-800 dark:border-gray-700 dark:text-white dark:hover:text-white dark:hover:bg-gray-700 dark:focus:ring-blue-500 dark:focus:text-white",
onclick: change_to_english,
label { { translate!(i18, "headers.language_buttons.english") } } },
button {
class: "px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-e-lg hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-2 focus:ring-blue-700 focus:text-blue-700 dark:bg-gray-800 dark:border-gray-700 dark:text-white dark:hover:text-white dark:hover:bg-gray-700 dark:focus:ring-blue-500 dark:focus:text-white",
onclick: change_to_german,
label { { translate!(i18, "headers.language_buttons.german") } } }
}
}
}

View File

@@ -1,7 +1,7 @@
use dioxus::prelude::*;
mod footer;
mod header;
pub mod footer;
pub mod header;
use crate::{Body, Route};
use footer::Footer;
@@ -9,17 +9,13 @@ use header::Header;
pub fn Layout() -> Element {
rsx! {
meta {
name: "description",
content: "Explore Tuan-Dat Tran's personal website featuring his CV, publications, projects, and consulting services. Get insights into his professional journey and connect for collaboration opportunities.",
}
div {
class: "flex flex-col min-h-screen" ,
class: "flex flex-col min-h-screen",
Header {},
Body {
Outlet::<Route> {},
}
Footer {},
},
Footer {}
}
}
}

80
src/login.rs Normal file
View File

@@ -0,0 +1,80 @@
use dioxus::prelude::*;
use dioxus_sdk::{i18n::use_i18, translate};
use crate::components::{H5, HR, P};
#[component]
pub fn Login() -> Element {
#[allow(unused_mut)]
let mut debug = use_signal(|| "".to_string());
let default_username = "Enter username here.";
let mut username = use_signal(|| default_username.to_string());
let mut password = use_signal(|| "".to_string());
let i18 = use_i18();
rsx! {
div {
class: "flex justify-center items-center w-full",
div {
class: "text-black bg-gray-800 px-10 py-8 rounded-xl w-screen shadow-xl max-w-sm",
div {
H5 { { translate!(i18, "login.title") } },
}
HR {},
div {
P { "Username" },
input {
class: if username.read().to_string() == default_username {
"pl-2 outline-none border-none w-full rounded-xl text-gray-400"
} else {
"pl-2 outline-none border-none w-full rounded-xl text-black"
},
value: "{username}",
oninput: move |event| username.set(event.value()),
onfocusin: move |_| {
if username.read().to_string() == default_username {
username.set("".to_string());
}
},
onfocusout: move |_| {
if username.read().to_string().is_empty() {
username.set(default_username.to_string());
}
},
onkeypress: move |event| async move {
if event.key() == Key::Enter {
let u = username.read().to_string();
let p = password.read().to_string();
login(u,p).await.unwrap();
}
},
}
div {
class: "py-2",
P { "Password" },
input {
class: "pl-2 outline-none border-none w-full rounded-xl text-gray-900",
value: "{password}",
oninput: move |event| password.set(event.value()),
onkeypress: move |event| async move {
if event.key() == Key::Enter {
let u = username.read().to_string();
let p = password.read().to_string();
login(u,p).await.unwrap();
}
}
}
}
}
},
"{debug}"
}
}
}
#[server(Login)]
async fn login(username: String, password: String) -> Result<(), ServerFnError> {
println!("Username: {}, Password: {}", username, password);
Ok(())
}

View File

@@ -2,27 +2,35 @@
use std::str::FromStr;
use components::H1;
use dioxus::prelude::*;
use dioxus_sdk::i18n::*;
use layout::footer::Footer;
use layout::header::Header;
use tracing::Level;
use axum::routing::*;
use axum_session::SessionConfig;
use axum_session::SessionStore;
use axum_session_auth::AuthConfig;
pub mod components;
mod consulting;
mod cv;
mod home;
mod impressum;
mod languages;
mod layout;
mod login;
mod publications;
use crate::consulting::Consulting;
use crate::cv::CV;
use crate::home::Home;
use crate::impressum::Impressum;
use crate::languages::DE_DE;
use crate::languages::EN_GB;
use crate::layout::Layout;
use crate::login::Login;
use crate::publications::PublicationsProjects;
#[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
@@ -36,8 +44,8 @@ pub enum Route {
PublicationsProjects {},
#[route("/resume")]
CV {},
#[route("/consulting")]
Consulting {},
#[route("/login")]
Login {},
#[end_layout]
#[route("/:..route")]
PageNotFound { route: Vec<String> },
@@ -60,6 +68,41 @@ fn App() -> Element {
});
rsx! {
meta {
name: "description",
content: "Visit Tuan-Dat Tran's website for his CV, publications, projects, and consulting services. Connect for collaboration.",
},
script {
r#type: "application/ld+json",
"
{{
\"@context\": \"https://schema.org\",
\"@type\": \"ProfilePage\",
\"mainEntity\": {{
\"@type\": \"Person\",
\"name\": \"Tuan-Dat Tran\",
\"alternateName\": \"TuDatTr\",
\"image\": \"https://www.tudattr.dev/pictures/headshot.webp\",
}}
}}
"
}
// meta {
// property: "og:title",
// content: "Tuan-Dat Trans Personal Website",
// }
// meta {
// property: "og:description",
// content: "Explore Tuan-Dat Tran's personal website featuring his CV, publications, projects, and consulting services. Get insights into his professional journey and connect for collaboration opportunities.",
// }
// meta {
// property: "og:image",
// content: "https://www.tudattr.dev/pictures/headshot.webp",
// }
// meta {
// property: "og:url",
// content: "https://tudattr.dev",
// }
div {
class: "bg-white dark:bg-gray-900 min-h-screen",
Router::<Route> {},
@@ -71,12 +114,16 @@ fn App() -> Element {
fn PageNotFound(route: Vec<String>) -> Element {
rsx! {
div {
class: "h-screen items-center justify-center",
img {
class: "size-auto",
src: "https://raw.githubusercontent.com/SAWARATSUKI/ServiceLogos/main/404Notfound/NotFound.png"
class: "flex flex-col min-h-screen items",
Header {},
div {
class: "container mx-auto p-4 flex items-center justify-center max-w-md w-full",
H1 {
"Site not found (404)"
},
}
}
Footer {}
},
}
}

View File

@@ -1,7 +1,7 @@
use dioxus::prelude::*;
use dioxus_sdk::{i18n::use_i18, translate};
use crate::components::{UnderConstruction, H1, HR};
use crate::components::{Bolding, UnderConstruction, H1, HR};
#[component]
pub fn PublicationsProjects() -> Element {
@@ -60,7 +60,7 @@ fn Publications() -> Element {
}
}
fn Publication(prop: PublicationProp) -> Element {
let pattern = "T.-D. Tran";
let pattern = vec!["T.-D. Tran".to_string(), "Tuan-Dat Tran".to_string()];
rsx! {
Link {
class:"block max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700",
@@ -73,9 +73,9 @@ fn Publication(prop: PublicationProp) -> Element {
span { class: "text-lg text-gray-900 dark:text-white", "{prop.conference}" },
p {
class:"font-normal text-gray-700 dark:text-gray-400 italic",
Authors {
Bolding {
authors: "{prop.authors}",
pattern: "{pattern}",
patterns: pattern,
},
}
p {
@@ -129,7 +129,7 @@ struct ProjectProp {
}
fn Project(prop: ProjectProp) -> Element {
let pattern = "T.-D. Tran";
let pattern = vec!["T.-D. Tran".to_string(), "Tuan-Dat Tran".to_string()];
rsx! {
Link {
@@ -143,9 +143,9 @@ fn Project(prop: ProjectProp) -> Element {
p { class: "text-lg text-gray-900 dark:text-white", "{prop.kind}" },
p {
class:"font-normal text-gray-700 dark:text-gray-400",
Authors {
Bolding {
authors: "{prop.authors}",
pattern: "{pattern}",
patterns: pattern,
},
}
p {
@@ -155,23 +155,3 @@ fn Project(prop: ProjectProp) -> Element {
}
}
}
#[derive(Clone, PartialEq, Props)]
struct AuthorProp {
authors: String,
pattern: String,
}
fn Authors(prop: AuthorProp) -> Element {
if let Some(start) = prop.authors.find(&prop.pattern) {
let end = start + prop.pattern.len();
let left = &prop.authors[..start];
let middle = &prop.authors[start..end];
let right = &prop.authors[end..];
rsx! {
"{left}" , b { "{middle}" }, "{right}",
}
} else {
rsx! { "{prop.authors}" }
}
}