mirror of
https://github.com/danbulant/Portfolio
synced 2026-07-05 19:11:06 +00:00
Merge remote-tracking branch 'origin' into admin_create_more_data
This commit is contained in:
commit
c5ac32d1fd
15 changed files with 353 additions and 61 deletions
|
|
@ -200,6 +200,19 @@ pub fn create_identity() -> (String, String) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn encrypt_buffer_with_recipients(
|
||||||
|
input_buffer: &[u8],
|
||||||
|
recipients: &Vec<String>,
|
||||||
|
) -> Result<Vec<u8>, ServiceError> {
|
||||||
|
let mut output_buffer = vec![];
|
||||||
|
age_encrypt_with_recipients(input_buffer,
|
||||||
|
&mut output_buffer,
|
||||||
|
&recipients.iter().map(|s| s.as_str()).collect()
|
||||||
|
).await?;
|
||||||
|
|
||||||
|
Ok(output_buffer)
|
||||||
|
}
|
||||||
|
|
||||||
async fn age_encrypt_with_recipients<W: tokio::io::AsyncWrite + Unpin>(
|
async fn age_encrypt_with_recipients<W: tokio::io::AsyncWrite + Unpin>(
|
||||||
input_buffer: &[u8],
|
input_buffer: &[u8],
|
||||||
output_buffer: &mut W,
|
output_buffer: &mut W,
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ mod tests {
|
||||||
|
|
||||||
let encrypted_details: EncryptedApplicationDetails = EncryptedApplicationDetails::new(
|
let encrypted_details: EncryptedApplicationDetails = EncryptedApplicationDetails::new(
|
||||||
&APPLICATION_DETAILS.lock().unwrap().clone(),
|
&APPLICATION_DETAILS.lock().unwrap().clone(),
|
||||||
vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_string()],
|
&vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_string()],
|
||||||
).await.unwrap();
|
).await.unwrap();
|
||||||
|
|
||||||
let candidate = Mutation::update_candidate_opt_details(&db, candidate, encrypted_details.candidate, 1).await.unwrap();
|
let candidate = Mutation::update_candidate_opt_details(&db, candidate, encrypted_details.candidate, 1).await.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ mod tests {
|
||||||
|
|
||||||
let encrypted_details: EncryptedApplicationDetails = EncryptedApplicationDetails::new(
|
let encrypted_details: EncryptedApplicationDetails = EncryptedApplicationDetails::new(
|
||||||
&APPLICATION_DETAILS.lock().unwrap().clone(),
|
&APPLICATION_DETAILS.lock().unwrap().clone(),
|
||||||
vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_string()],
|
&vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_string()],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -321,7 +321,7 @@ impl From<&parent::Model> for EncryptedParentDetails {
|
||||||
impl EncryptedApplicationDetails {
|
impl EncryptedApplicationDetails {
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
form: &ApplicationDetails,
|
form: &ApplicationDetails,
|
||||||
recipients: Vec<String>,
|
recipients: &Vec<String>,
|
||||||
) -> Result<EncryptedApplicationDetails, ServiceError> {
|
) -> Result<EncryptedApplicationDetails, ServiceError> {
|
||||||
let candidate = EncryptedCandidateDetails::new(&form.candidate, &recipients).await?;
|
let candidate = EncryptedCandidateDetails::new(&form.candidate, &recipients).await?;
|
||||||
let enc_parents = future::try_join_all(
|
let enc_parents = future::try_join_all(
|
||||||
|
|
@ -459,7 +459,7 @@ pub mod tests {
|
||||||
async fn test_encrypted_application_details_new() {
|
async fn test_encrypted_application_details_new() {
|
||||||
let encrypted_details = EncryptedApplicationDetails::new(
|
let encrypted_details = EncryptedApplicationDetails::new(
|
||||||
&APPLICATION_DETAILS.lock().unwrap().clone(),
|
&APPLICATION_DETAILS.lock().unwrap().clone(),
|
||||||
vec![PUBLIC_KEY.to_string()],
|
&vec![PUBLIC_KEY.to_string()],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -488,7 +488,7 @@ pub mod tests {
|
||||||
async fn test_encrypted_application_details_decrypt() {
|
async fn test_encrypted_application_details_decrypt() {
|
||||||
let encrypted_details = EncryptedApplicationDetails::new(
|
let encrypted_details = EncryptedApplicationDetails::new(
|
||||||
&APPLICATION_DETAILS.lock().unwrap().clone(),
|
&APPLICATION_DETAILS.lock().unwrap().clone(),
|
||||||
vec![PUBLIC_KEY.to_string()],
|
&vec![PUBLIC_KEY.to_string()],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use sea_orm::{DbConn, prelude::Uuid, IntoActiveModel};
|
||||||
|
|
||||||
use crate::{error::ServiceError, Query, utils::db::get_recipients, models::candidate_details::EncryptedApplicationDetails, models::{candidate::{ApplicationDetails, CreateCandidateResponse}, candidate_details::{EncryptedString, EncryptedCandidateDetails}, auth::AuthenticableTrait, application::ApplicationResponse}, Mutation, crypto::{hash_password, self}};
|
use crate::{error::ServiceError, Query, utils::db::get_recipients, models::candidate_details::EncryptedApplicationDetails, models::{candidate::{ApplicationDetails, CreateCandidateResponse}, candidate_details::{EncryptedString, EncryptedCandidateDetails}, auth::AuthenticableTrait, application::ApplicationResponse}, Mutation, crypto::{hash_password, self}};
|
||||||
|
|
||||||
use super::{parent_service::ParentService, candidate_service::CandidateService, session_service::SessionService, portfolio_service::PortfolioService};
|
use super::{parent_service::ParentService, candidate_service::CandidateService, session_service::SessionService, portfolio_service::{PortfolioService, SubmissionProgress}};
|
||||||
|
|
||||||
const FIELD_OF_STUDY_PREFIXES: [&str; 3] = ["101", "102", "103"];
|
const FIELD_OF_STUDY_PREFIXES: [&str; 3] = ["101", "102", "103"];
|
||||||
|
|
||||||
|
|
@ -281,8 +281,6 @@ impl ApplicationService {
|
||||||
).await
|
).await
|
||||||
})
|
})
|
||||||
).await
|
).await
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn decrypt_private_key(
|
async fn decrypt_private_key(
|
||||||
|
|
@ -307,7 +305,6 @@ impl ApplicationService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
pub async fn reset_password(
|
pub async fn reset_password(
|
||||||
admin_private_key: String,
|
admin_private_key: String,
|
||||||
db: &DbConn,
|
db: &DbConn,
|
||||||
|
|
@ -316,7 +313,6 @@ impl ApplicationService {
|
||||||
let application = Query::find_application_by_id(db, id).await?
|
let application = Query::find_application_by_id(db, id).await?
|
||||||
.ok_or(ServiceError::CandidateNotFound)?;
|
.ok_or(ServiceError::CandidateNotFound)?;
|
||||||
let candidate = ApplicationService::find_related_candidate(db, &application).await?;
|
let candidate = ApplicationService::find_related_candidate(db, &application).await?;
|
||||||
let parents = Query::find_candidate_parents(db, &candidate).await?;
|
|
||||||
|
|
||||||
let new_password_plain = crypto::random_12_char_string();
|
let new_password_plain = crypto::random_12_char_string();
|
||||||
let new_password_hash = crypto::hash_password(new_password_plain.clone()).await?;
|
let new_password_hash = crypto::hash_password(new_password_plain.clone()).await?;
|
||||||
|
|
@ -335,6 +331,7 @@ impl ApplicationService {
|
||||||
encrypted_priv_key
|
encrypted_priv_key
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
|
|
||||||
// user might no have filled his details yet, but personal id number is filled from beginning
|
// user might no have filled his details yet, but personal id number is filled from beginning
|
||||||
let personal_id_number = EncryptedString::from(application.personal_id_number.clone())
|
let personal_id_number = EncryptedString::from(application.personal_id_number.clone())
|
||||||
.decrypt(&admin_private_key)
|
.decrypt(&admin_private_key)
|
||||||
|
|
@ -346,25 +343,19 @@ impl ApplicationService {
|
||||||
recipients.append(&mut admin_public_keys);
|
recipients.append(&mut admin_public_keys);
|
||||||
recipients.append(&mut applications.iter().map(|a| a.public_key.to_owned()).collect());
|
recipients.append(&mut applications.iter().map(|a| a.public_key.to_owned()).collect());
|
||||||
|
|
||||||
let dec_details = EncryptedApplicationDetails::from((&candidate, &parents))
|
let candidate = Self::update_all_application_details(db,
|
||||||
.decrypt(admin_private_key).await?;
|
application.id,
|
||||||
|
candidate,
|
||||||
let enc_details = EncryptedApplicationDetails::new(&dec_details, recipients).await?;
|
&recipients,
|
||||||
|
&admin_private_key
|
||||||
let candidate = Mutation::update_personal_id(db,
|
|
||||||
candidate,
|
|
||||||
&enc_details.candidate.personal_id_number.to_owned()
|
|
||||||
.ok_or(ServiceError::CandidateDetailsNotSet)?.to_string()
|
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
Mutation::update_candidate_opt_details(db,
|
if PortfolioService::get_submission_progress(candidate.id).await? == SubmissionProgress::Submitted {
|
||||||
candidate,
|
PortfolioService::reencrypt_portfolio(
|
||||||
enc_details.candidate,
|
candidate.id,
|
||||||
application.id
|
admin_private_key,
|
||||||
).await?;
|
&recipients
|
||||||
|
).await?;
|
||||||
for i in 0..enc_details.parents.len() {
|
|
||||||
Mutation::add_parent_details(db, parents[i].clone(), enc_details.parents[i].clone()).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
|
|
@ -379,6 +370,37 @@ impl ApplicationService {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn update_all_application_details(db: &DbConn,
|
||||||
|
application_id: i32,
|
||||||
|
candidate: candidate::Model,
|
||||||
|
recipients: &Vec<String>,
|
||||||
|
admin_private_key: &String
|
||||||
|
) -> Result<candidate::Model, ServiceError> {
|
||||||
|
let parents = Query::find_candidate_parents(db, &candidate).await?;
|
||||||
|
let dec_details = EncryptedApplicationDetails::from((&candidate, &parents))
|
||||||
|
.decrypt(admin_private_key.to_owned()).await?;
|
||||||
|
|
||||||
|
let enc_details = EncryptedApplicationDetails::new(&dec_details, recipients).await?;
|
||||||
|
|
||||||
|
let candidate = Mutation::update_personal_id(db,
|
||||||
|
candidate,
|
||||||
|
&enc_details.candidate.personal_id_number.to_owned()
|
||||||
|
.ok_or(ServiceError::CandidateDetailsNotSet)?.to_string()
|
||||||
|
).await?;
|
||||||
|
|
||||||
|
let candidate = Mutation::update_candidate_opt_details(db,
|
||||||
|
candidate,
|
||||||
|
enc_details.candidate,
|
||||||
|
application_id
|
||||||
|
).await?;
|
||||||
|
|
||||||
|
for i in 0..enc_details.parents.len() {
|
||||||
|
Mutation::add_parent_details(db, parents[i].clone(), enc_details.parents[i].clone()).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(candidate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|
@ -477,7 +499,6 @@ mod application_tests {
|
||||||
assert!(!ApplicationService::is_application_id_valid(101));
|
assert!(!ApplicationService::is_application_id_valid(101));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_password_reset() {
|
async fn test_password_reset() {
|
||||||
let db = get_memory_sqlite_connection().await;
|
let db = get_memory_sqlite_connection().await;
|
||||||
|
|
|
||||||
|
|
@ -368,15 +368,46 @@ impl PortfolioService {
|
||||||
/// Returns decrypted portfolio zip as Vec of bytes
|
/// Returns decrypted portfolio zip as Vec of bytes
|
||||||
pub async fn get_portfolio(candidate_id: i32, private_key: String) -> Result<Vec<u8>, ServiceError> {
|
pub async fn get_portfolio(candidate_id: i32, private_key: String) -> Result<Vec<u8>, ServiceError> {
|
||||||
info!("PORTFOLIO {} DECRYPT STARTED", candidate_id);
|
info!("PORTFOLIO {} DECRYPT STARTED", candidate_id);
|
||||||
let path = Self::get_file_store_path().join(&candidate_id.to_string()).to_path_buf();
|
let path = Self::get_file_store_path()
|
||||||
|
.join(&candidate_id.to_string())
|
||||||
let path = path.join(FileType::Age.as_str());
|
.join(FileType::Age.as_str())
|
||||||
|
.to_path_buf();
|
||||||
|
|
||||||
let buffer = crypto::decrypt_file_with_private_key_as_buffer(path, &private_key).await?;
|
let buffer = crypto::decrypt_file_with_private_key_as_buffer(path, &private_key).await?;
|
||||||
|
|
||||||
info!("PORTFOLIO {} DECRYPT FINISHED", candidate_id);
|
info!("PORTFOLIO {} DECRYPT FINISHED", candidate_id);
|
||||||
Ok(buffer)
|
Ok(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn reencrypt_portfolio(candidate_id: i32,
|
||||||
|
private_key: String,
|
||||||
|
recipients: &Vec<String>
|
||||||
|
) -> Result<(), ServiceError> {
|
||||||
|
info!("PORTFOLIO {} REENCRYPT STARTED", candidate_id);
|
||||||
|
let path = Self::get_file_store_path()
|
||||||
|
.join(&candidate_id.to_string())
|
||||||
|
.join(FileType::Age.as_str())
|
||||||
|
.to_path_buf();
|
||||||
|
|
||||||
|
let plain_portfolio = crypto::decrypt_file_with_private_key_as_buffer(
|
||||||
|
path.to_owned(),
|
||||||
|
&private_key
|
||||||
|
).await?;
|
||||||
|
|
||||||
|
|
||||||
|
let enc_portfolio= crypto::encrypt_buffer_with_recipients(
|
||||||
|
&plain_portfolio,
|
||||||
|
recipients
|
||||||
|
).await?;
|
||||||
|
|
||||||
|
tokio::fs::remove_file(path.to_owned()).await?;
|
||||||
|
|
||||||
|
tokio::fs::write(path, enc_portfolio).await?;
|
||||||
|
|
||||||
|
info!("PORTFOLIO {} REENCRYPT FINISHED", candidate_id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import LL from '$i18n/i18n-svelte';
|
||||||
|
|
||||||
import { fetchSubmProgress } from '$lib/stores/portfolio';
|
import { fetchSubmProgress } from '$lib/stores/portfolio';
|
||||||
import { apiDeleteCoverLetter, apiUploadCoverLetter } from '$lib/@api/candidate';
|
import { apiDeleteCoverLetter, apiUploadCoverLetter } from '$lib/@api/candidate';
|
||||||
import DashboardUploadCard from './DashboardUploadCard.svelte';
|
import DashboardUploadCard from './DashboardUploadCard.svelte';
|
||||||
|
|
@ -28,9 +30,9 @@
|
||||||
{error}
|
{error}
|
||||||
on:filedrop={(e) => onFileDrop(e.detail)}
|
on:filedrop={(e) => onFileDrop(e.detail)}
|
||||||
on:delete={onDelete}
|
on:delete={onDelete}
|
||||||
title="Motivační dopis"
|
title={$LL.components.dashboard.coverLetterUploadCard.title()}
|
||||||
filetype="PDF"
|
filetype="PDF"
|
||||||
filesize={10}
|
filesize={10}
|
||||||
fileType={1}
|
fileType={1}
|
||||||
placeholder="svůj motivanční dopis"
|
placeholder={$LL.components.dashboard.coverLetterUploadCard.placeholder()}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import LL from '$i18n/i18n-svelte';
|
||||||
|
|
||||||
import debounce from 'just-debounce-it';
|
import debounce from 'just-debounce-it';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
@ -7,7 +9,6 @@
|
||||||
apiLogout,
|
apiLogout,
|
||||||
apiSubmitPortfolio
|
apiSubmitPortfolio
|
||||||
} from '$lib/@api/candidate';
|
} from '$lib/@api/candidate';
|
||||||
import Circles from '$lib/components/icons/Circles.svelte';
|
|
||||||
import { fetchSubmProgress, type Status } from '$lib/stores/portfolio';
|
import { fetchSubmProgress, type Status } from '$lib/stores/portfolio';
|
||||||
import StatusNotificationBig from './StatusNotificationBig.svelte';
|
import StatusNotificationBig from './StatusNotificationBig.svelte';
|
||||||
import InfoButton from './InfoButton.svelte';
|
import InfoButton from './InfoButton.svelte';
|
||||||
|
|
@ -150,39 +151,39 @@
|
||||||
class="mt-4 flex flex-col justify-between leading-10"
|
class="mt-4 flex flex-col justify-between leading-10"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
>Ev. č. přihlášky ({getField($baseCandidateData.applications[0])}):
|
>{$LL.input.evidenceNumber()} ({getField($baseCandidateData.applications[0])}):
|
||||||
<span class="font-bold">{$baseCandidateData.applications[0]}</span></span
|
<span class="font-bold">{$baseCandidateData.applications[0]}</span></span
|
||||||
>
|
>
|
||||||
{#if $baseCandidateData.applications.length > 1}
|
{#if $baseCandidateData.applications.length > 1}
|
||||||
<span
|
<span
|
||||||
>Ev. č. přihlášky ({getField($baseCandidateData.applications[1])}):
|
>{$LL.input.evidenceNumber()} ({getField($baseCandidateData.applications[1])}):
|
||||||
<span class="font-bold">{$baseCandidateData.applications[1]}</span></span
|
<span class="font-bold">{$baseCandidateData.applications[1]}</span></span
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
<span>Adresa: <span class="font-bold">{$candidateData.candidate.address}</span></span>
|
<span>{$LL.input.address()}: <span class="font-bold">{$candidateData.candidate.address}</span></span>
|
||||||
<span
|
<span
|
||||||
>Datum narození: <span class="font-bold">{$candidateData.candidate.birthdate}</span
|
>{$LL.input.birthDate()}: <span class="font-bold">{$candidateData.candidate.birthdate}</span
|
||||||
></span
|
></span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
>Místo narození: <span class="font-bold">{$candidateData.candidate.birthplace}</span
|
>{$LL.input.birthPlace()}: <span class="font-bold">{$candidateData.candidate.birthplace}</span
|
||||||
></span
|
></span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
>Rodné číslo: <span class="font-bold"
|
>{$LL.input.personalIdentificationNumber()}: <span class="font-bold"
|
||||||
>{$candidateData.candidate.personalIdNumber}</span
|
>{$candidateData.candidate.personalIdNumber}</span
|
||||||
></span
|
></span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
>IČO/Název školy: <span class="font-bold">{$candidateData.candidate.schoolName}</span
|
>{$LL.input.schoolIzo()}: <span class="font-bold">{$candidateData.candidate.schoolName}</span
|
||||||
></span
|
></span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
>Číslo zdravotní pojišťovny: <span class="font-bold"
|
>{$LL.input.insuranceNumber()}: <span class="font-bold"
|
||||||
>{$candidateData.candidate.healthInsurance}</span
|
>{$candidateData.candidate.healthInsurance}</span
|
||||||
></span
|
></span
|
||||||
>
|
>
|
||||||
<span>Telefon: <span class="font-bold">{$candidateData.candidate.telephone}</span></span
|
<span>{$LL.input.telephone()}: <span class="font-bold">{$candidateData.candidate.telephone}</span></span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
@ -200,8 +201,8 @@
|
||||||
<span class="text-sspsBlue text-xl font-bold"
|
<span class="text-sspsBlue text-xl font-bold"
|
||||||
>{parent.name + ' ' + parent.surname}</span
|
>{parent.name + ' ' + parent.surname}</span
|
||||||
>
|
>
|
||||||
<span>Email: <span class="font-bold">{parent.email}</span></span>
|
<span>{$LL.input.email()}: <span class="font-bold">{parent.email}</span></span>
|
||||||
<span>Telefon: <span class="font-bold">{parent.telephone}</span></span>
|
<span>{$LL.input.telephone()}: <span class="font-bold">{parent.telephone}</span></span>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import LL from '$i18n/i18n-svelte';
|
||||||
|
|
||||||
import FileType from './FileType.svelte';
|
import FileType from './FileType.svelte';
|
||||||
import debounce from 'just-debounce-it';
|
import debounce from 'just-debounce-it';
|
||||||
import { filedrop, type FileDropOptions, type Files } from 'filedrop-svelte';
|
import { filedrop, type FileDropOptions, type Files } from 'filedrop-svelte';
|
||||||
|
|
@ -103,7 +105,7 @@
|
||||||
{#if status === 'uploaded'}
|
{#if status === 'uploaded'}
|
||||||
<button
|
<button
|
||||||
class="mr-3 rounded-xl bg-[#ef8b46] py-0.5 px-2 text-white shadow-md transition-all duration-300 hover:bg-orange-400"
|
class="mr-3 rounded-xl bg-[#ef8b46] py-0.5 px-2 text-white shadow-md transition-all duration-300 hover:bg-orange-400"
|
||||||
on:click={debounce(() => dispatch('delete'), 150)}>Smazat</button
|
on:click={debounce(() => dispatch('delete'), 150)}>{$LL.components.dashboard.dashboardUploadCard.delete()}</button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
<StatusNotificationDot {status} />
|
<StatusNotificationDot {status} />
|
||||||
|
|
@ -131,9 +133,9 @@
|
||||||
>
|
>
|
||||||
<div class="hidden items-center xl:block">
|
<div class="hidden items-center xl:block">
|
||||||
{#if bytesTotal === 0 || Math.round(progress * 100) === 100}
|
{#if bytesTotal === 0 || Math.round(progress * 100) === 100}
|
||||||
<h2 class="text-xl font-bold">{status === 'submitted' ? 'Odesláno' : 'Nahráno'}</h2>
|
<h2 class="text-xl font-bold">{status === 'submitted' ? $LL.components.dashboard.dashboardUploadCard.sent() : $LL.components.dashboard.dashboardUploadCard.uploaded()}</h2>
|
||||||
{:else}
|
{:else}
|
||||||
<h2 class="text-xl">Nahráno {((bytesTotal / 1_000_000) * progress).toFixed(1)} MB</h2>
|
<h2 class="text-xl">{$LL.components.dashboard.dashboardUploadCard.uploaded()} {((bytesTotal / 1_000_000) * progress).toFixed(1)} MB</h2>
|
||||||
<h2 class="self-center text-xl">z {(bytesTotal / 1_000_000).toFixed(1)} MB</h2>
|
<h2 class="self-center text-xl">z {(bytesTotal / 1_000_000).toFixed(1)} MB</h2>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -168,8 +170,14 @@
|
||||||
{#if error}
|
{#if error}
|
||||||
<span class="font-semibold text-red-600">{error}</span>
|
<span class="font-semibold text-red-600">{error}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="text-[#406280]">Sem přetáhněte,</span>
|
<span class="text-[#406280]"
|
||||||
<span class="text-sspsGray">nebo nahrajte {placeholder}</span>
|
>{$LL.components.dashboard.dashboardUploadCard.dropHere()}</span
|
||||||
|
>
|
||||||
|
<span class="text-sspsGray"
|
||||||
|
>{$LL.components.dashboard.dashboardUploadCard.orUpload({
|
||||||
|
placeholder
|
||||||
|
})}</span
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import LL from '$i18n/i18n-svelte';
|
||||||
|
|
||||||
import type { ApiError } from '$lib/@api';
|
import type { ApiError } from '$lib/@api';
|
||||||
import { fetchSubmProgress } from '$lib/stores/portfolio';
|
import { fetchSubmProgress } from '$lib/stores/portfolio';
|
||||||
import { apiDeletePortfolioLetter, apiUploadPortfolioLetter } from '../../@api/candidate';
|
import { apiDeletePortfolioLetter, apiUploadPortfolioLetter } from '../../@api/candidate';
|
||||||
|
|
@ -28,9 +30,9 @@
|
||||||
{error}
|
{error}
|
||||||
on:filedrop={(e) => onFileDrop(e.detail)}
|
on:filedrop={(e) => onFileDrop(e.detail)}
|
||||||
on:delete={onDelete}
|
on:delete={onDelete}
|
||||||
title="Portfolio"
|
title={$LL.components.dashboard.portfolioLetterUploadCard.title()}
|
||||||
filetype="PDF"
|
filetype="PDF"
|
||||||
filesize={10}
|
filesize={10}
|
||||||
fileType={2}
|
fileType={2}
|
||||||
placeholder="svoje portfolio"
|
placeholder={$LL.components.dashboard.portfolioLetterUploadCard.placeholder()}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import LL from '$i18n/i18n-svelte';
|
||||||
|
|
||||||
import { fetchSubmProgress } from '$lib/stores/portfolio';
|
import { fetchSubmProgress } from '$lib/stores/portfolio';
|
||||||
import { apiDeletePortfolioZip, apiUploadPortfolioZip } from '$lib/@api/candidate';
|
import { apiDeletePortfolioZip, apiUploadPortfolioZip } from '$lib/@api/candidate';
|
||||||
import DashboardUploadCard from './DashboardUploadCard.svelte';
|
import DashboardUploadCard from './DashboardUploadCard.svelte';
|
||||||
|
|
@ -28,9 +30,9 @@
|
||||||
{error}
|
{error}
|
||||||
on:filedrop={(e) => onFileDrop(e.detail)}
|
on:filedrop={(e) => onFileDrop(e.detail)}
|
||||||
on:delete={onDelete}
|
on:delete={onDelete}
|
||||||
title="Další data"
|
title={$LL.components.dashboard.portfolioZipUploadCard.title()}
|
||||||
filetype="ZIP"
|
filetype="ZIP"
|
||||||
filesize={100}
|
filesize={100}
|
||||||
fileType={3}
|
fileType={3}
|
||||||
placeholder="vaše další soubory ve formátu ZIP"
|
placeholder={$LL.components.dashboard.portfolioZipUploadCard.placeholder()}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import LL from '$i18n/i18n-svelte';
|
||||||
|
|
||||||
import type { Status } from '$lib/stores/portfolio';
|
import type { Status } from '$lib/stores/portfolio';
|
||||||
|
|
||||||
export let loading: boolean = false;
|
export let loading: boolean = false;
|
||||||
|
|
@ -8,16 +10,16 @@
|
||||||
let description: string;
|
let description: string;
|
||||||
$: switch (status) {
|
$: switch (status) {
|
||||||
case 'submitted':
|
case 'submitted':
|
||||||
title = 'Soubory odevzdány!';
|
title = $LL.components.dashboard.statusNotificationBig.submitted.title();
|
||||||
description = 'Vaše soubory smažete kliknutím zde';
|
description = $LL.components.dashboard.statusNotificationBig.submitted.description();
|
||||||
break;
|
break;
|
||||||
case 'uploaded':
|
case 'uploaded':
|
||||||
title = 'Soubory nebyly odevzdány!';
|
title = $LL.components.dashboard.statusNotificationBig.uploaded.title();
|
||||||
description = 'Odevzdejte soubory kliknutím zde';
|
description = $LL.components.dashboard.statusNotificationBig.uploaded.description();
|
||||||
break;
|
break;
|
||||||
case 'missing':
|
case 'missing':
|
||||||
title = 'Soubory nebyly nahrány!';
|
title = $LL.components.dashboard.statusNotificationBig.missing.title();
|
||||||
description = 'Nahrajte včechny soubory prosím';
|
description = $LL.components.dashboard.statusNotificationBig.missing.description();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -423,6 +423,7 @@
|
||||||
<form on:submit={handleSubmit} id="triggerForm" class="invisible hidden" />
|
<form on:submit={handleSubmit} id="triggerForm" class="invisible hidden" />
|
||||||
{#if pageIndex === 0}
|
{#if pageIndex === 0}
|
||||||
<form on:submit={handleSubmit}>
|
<form on:submit={handleSubmit}>
|
||||||
|
<h1 class="title mt-8">{$LL.candidate.register.first.title()}</h1>
|
||||||
<h1 class="title mt-8">{$LL.candidate.register.first.title()}</h1>
|
<h1 class="title mt-8">{$LL.candidate.register.first.title()}</h1>
|
||||||
<p class="description mt-8 block text-center">
|
<p class="description mt-8 block text-center">
|
||||||
{$LL.candidate.register.first.description()}
|
{$LL.candidate.register.first.description()}
|
||||||
|
|
@ -467,6 +468,7 @@
|
||||||
<h1 class="title mt-8">{pageTexts[1]}</h1>
|
<h1 class="title mt-8">{pageTexts[1]}</h1>
|
||||||
<p class="description mt-8 block text-center">
|
<p class="description mt-8 block text-center">
|
||||||
{$LL.candidate.register.third.description()}
|
{$LL.candidate.register.third.description()}
|
||||||
|
{$LL.candidate.register.third.description()}
|
||||||
</p>
|
</p>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,41 @@ const cs: BaseTranslation = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
dashboard: {
|
||||||
|
coverLetterUploadCard: {
|
||||||
|
title: 'Motivační dopis',
|
||||||
|
placeholder: 'svůj motivanční dopis'
|
||||||
|
},
|
||||||
|
portfolioLetterUploadCard: {
|
||||||
|
title: 'Portfolio',
|
||||||
|
placeholder: 'své portfolio'
|
||||||
|
},
|
||||||
|
portfolioZipUploadCard: {
|
||||||
|
title: 'Další data',
|
||||||
|
placeholder: 'vaše další soubory ve formátu ZIP'
|
||||||
|
},
|
||||||
|
dashboardUploadCard: {
|
||||||
|
dropHere: 'Sem přetáhněte,',
|
||||||
|
orUpload: 'Nebo nahrajte {placeholder:string}',
|
||||||
|
uploaded: 'Nahráno',
|
||||||
|
sent: 'Odesláno',
|
||||||
|
delete: 'Smazat'
|
||||||
|
},
|
||||||
|
statusNotificationBig: {
|
||||||
|
submitted: {
|
||||||
|
title: 'Soubory odevzdány!',
|
||||||
|
description: 'Vaše soubory smažete kliknutím zde'
|
||||||
|
},
|
||||||
|
uploaded: {
|
||||||
|
title: 'Soubory nebyly odevzdány!',
|
||||||
|
description: 'Odevzdejte soubory kliknutím zde'
|
||||||
|
},
|
||||||
|
missing: {
|
||||||
|
title: 'Soubory nebyly nahrány!',
|
||||||
|
description: 'Nahrajte včechny soubory prosím',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
checkbox: {
|
checkbox: {
|
||||||
accountLinkCheckBox: {
|
accountLinkCheckBox: {
|
||||||
ok: 'Vše je v pořádku',
|
ok: 'Vše je v pořádku',
|
||||||
|
|
@ -117,7 +152,7 @@ const cs: BaseTranslation = {
|
||||||
adminId: 'Admin Id',
|
adminId: 'Admin Id',
|
||||||
password: 'Heslo',
|
password: 'Heslo',
|
||||||
submit: 'Odeslat',
|
submit: 'Odeslat',
|
||||||
continue: "Pokračovat",
|
continue: 'Pokračovat',
|
||||||
parent: {
|
parent: {
|
||||||
nameSurname: 'Jméno a příjmení zákonného zástupce',
|
nameSurname: 'Jméno a příjmení zákonného zástupce',
|
||||||
email: 'E-mail zákonného zástupce',
|
email: 'E-mail zákonného zástupce',
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,93 @@ type RootTranslation = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
components: {
|
components: {
|
||||||
|
dashboard: {
|
||||||
|
coverLetterUploadCard: {
|
||||||
|
/**
|
||||||
|
* Motivační dopis
|
||||||
|
*/
|
||||||
|
title: string
|
||||||
|
/**
|
||||||
|
* svůj motivanční dopis
|
||||||
|
*/
|
||||||
|
placeholder: string
|
||||||
|
}
|
||||||
|
portfolioLetterUploadCard: {
|
||||||
|
/**
|
||||||
|
* Portfolio
|
||||||
|
*/
|
||||||
|
title: string
|
||||||
|
/**
|
||||||
|
* své portfolio
|
||||||
|
*/
|
||||||
|
placeholder: string
|
||||||
|
}
|
||||||
|
portfolioZipUploadCard: {
|
||||||
|
/**
|
||||||
|
* Další data
|
||||||
|
*/
|
||||||
|
title: string
|
||||||
|
/**
|
||||||
|
* vaše další soubory ve formátu ZIP
|
||||||
|
*/
|
||||||
|
placeholder: string
|
||||||
|
}
|
||||||
|
dashboardUploadCard: {
|
||||||
|
/**
|
||||||
|
* Sem přetáhněte,
|
||||||
|
*/
|
||||||
|
dropHere: string
|
||||||
|
/**
|
||||||
|
* Nebo nahrajte {placeholder}
|
||||||
|
* @param {string} placeholder
|
||||||
|
*/
|
||||||
|
orUpload: RequiredParams<'placeholder'>
|
||||||
|
/**
|
||||||
|
* Nahráno
|
||||||
|
*/
|
||||||
|
uploaded: string
|
||||||
|
/**
|
||||||
|
* Odesláno
|
||||||
|
*/
|
||||||
|
sent: string
|
||||||
|
/**
|
||||||
|
* Smazat
|
||||||
|
*/
|
||||||
|
'delete': string
|
||||||
|
}
|
||||||
|
statusNotificationBig: {
|
||||||
|
submitted: {
|
||||||
|
/**
|
||||||
|
* Soubory odevzdány!
|
||||||
|
*/
|
||||||
|
title: string
|
||||||
|
/**
|
||||||
|
* Vaše soubory smažete kliknutím zde
|
||||||
|
*/
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
uploaded: {
|
||||||
|
/**
|
||||||
|
* Soubory nebyly odevzdány!
|
||||||
|
*/
|
||||||
|
title: string
|
||||||
|
/**
|
||||||
|
* Odevzdejte soubory kliknutím zde
|
||||||
|
*/
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
missing: {
|
||||||
|
/**
|
||||||
|
* Soubory nebyly nahrány!
|
||||||
|
*/
|
||||||
|
title: string
|
||||||
|
/**
|
||||||
|
* Nahrajte včechny soubory prosím
|
||||||
|
*/
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
checkbox: {
|
checkbox: {
|
||||||
accountLinkCheckBox: {
|
accountLinkCheckBox: {
|
||||||
/**
|
/**
|
||||||
|
|
@ -461,6 +548,92 @@ export type TranslationFunctions = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
components: {
|
components: {
|
||||||
|
dashboard: {
|
||||||
|
coverLetterUploadCard: {
|
||||||
|
/**
|
||||||
|
* Motivační dopis
|
||||||
|
*/
|
||||||
|
title: () => LocalizedString
|
||||||
|
/**
|
||||||
|
* svůj motivanční dopis
|
||||||
|
*/
|
||||||
|
placeholder: () => LocalizedString
|
||||||
|
}
|
||||||
|
portfolioLetterUploadCard: {
|
||||||
|
/**
|
||||||
|
* Portfolio
|
||||||
|
*/
|
||||||
|
title: () => LocalizedString
|
||||||
|
/**
|
||||||
|
* své portfolio
|
||||||
|
*/
|
||||||
|
placeholder: () => LocalizedString
|
||||||
|
}
|
||||||
|
portfolioZipUploadCard: {
|
||||||
|
/**
|
||||||
|
* Další data
|
||||||
|
*/
|
||||||
|
title: () => LocalizedString
|
||||||
|
/**
|
||||||
|
* vaše další soubory ve formátu ZIP
|
||||||
|
*/
|
||||||
|
placeholder: () => LocalizedString
|
||||||
|
}
|
||||||
|
dashboardUploadCard: {
|
||||||
|
/**
|
||||||
|
* Sem přetáhněte,
|
||||||
|
*/
|
||||||
|
dropHere: () => LocalizedString
|
||||||
|
/**
|
||||||
|
* Nebo nahrajte {placeholder}
|
||||||
|
*/
|
||||||
|
orUpload: (arg: { placeholder: string }) => LocalizedString
|
||||||
|
/**
|
||||||
|
* Nahráno
|
||||||
|
*/
|
||||||
|
uploaded: () => LocalizedString
|
||||||
|
/**
|
||||||
|
* Odesláno
|
||||||
|
*/
|
||||||
|
sent: () => LocalizedString
|
||||||
|
/**
|
||||||
|
* Smazat
|
||||||
|
*/
|
||||||
|
'delete': () => LocalizedString
|
||||||
|
}
|
||||||
|
statusNotificationBig: {
|
||||||
|
submitted: {
|
||||||
|
/**
|
||||||
|
* Soubory odevzdány!
|
||||||
|
*/
|
||||||
|
title: () => LocalizedString
|
||||||
|
/**
|
||||||
|
* Vaše soubory smažete kliknutím zde
|
||||||
|
*/
|
||||||
|
description: () => LocalizedString
|
||||||
|
}
|
||||||
|
uploaded: {
|
||||||
|
/**
|
||||||
|
* Soubory nebyly odevzdány!
|
||||||
|
*/
|
||||||
|
title: () => LocalizedString
|
||||||
|
/**
|
||||||
|
* Odevzdejte soubory kliknutím zde
|
||||||
|
*/
|
||||||
|
description: () => LocalizedString
|
||||||
|
}
|
||||||
|
missing: {
|
||||||
|
/**
|
||||||
|
* Soubory nebyly nahrány!
|
||||||
|
*/
|
||||||
|
title: () => LocalizedString
|
||||||
|
/**
|
||||||
|
* Nahrajte včechny soubory prosím
|
||||||
|
*/
|
||||||
|
description: () => LocalizedString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
checkbox: {
|
checkbox: {
|
||||||
accountLinkCheckBox: {
|
accountLinkCheckBox: {
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue