Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions apps/desktop/src-tauri/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,39 @@ pub async fn fetch_organizations(app: &AppHandle) -> Result<Vec<Organization>, A
.await
.map_err(|err| format!("api/fetch_organizations/response: {err}").into())
}

#[derive(Serialize, Deserialize, Type, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Workspace {
pub id: String,
pub name: String,
pub avatar_url: Option<String>,
}

pub async fn fetch_workspaces(app: &AppHandle) -> Result<Vec<Workspace>, AuthedApiError> {
#[derive(Deserialize)]
struct Response {
workspaces: Vec<Workspace>,
}

let resp = app
.authed_api_request("/api/desktop/workspaces", |client, url| client.get(url))
.await
.map_err(|err| format!("api/fetch_workspaces/request: {err}"))?;

if !resp.status().is_success() {
let status = resp.status().as_u16();
let error_body = resp
.text()
.await
.unwrap_or_else(|_| "<no response body>".to_string());
return Err(format!("api/fetch_workspaces/{status}: {error_body}").into());
}

let response: Response = resp
.json()
.await
.map_err(|err| format!("api/fetch_workspaces/response: {err}"))?;

Ok(response.workspaces)
}
75 changes: 42 additions & 33 deletions apps/desktop/src-tauri/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use tauri_plugin_store::StoreExt;
use web_api::ManagerExt;

use crate::{
api::{self, Organization},
api::{self, Organization, Workspace},
web_api,
};

Expand All @@ -19,6 +19,8 @@ pub struct AuthStore {
pub intercom_hash: Option<String>,
#[serde(default)]
pub organizations: Vec<Organization>,
#[serde(default)]
pub workspaces: Vec<Workspace>,
}

#[derive(Serialize, Deserialize, Type, Debug)]
Expand Down Expand Up @@ -69,39 +71,46 @@ impl AuthStore {
}

let mut auth = auth;
println!(
"Fetching plan for user {}",
auth.user_id.as_deref().unwrap_or("unknown")
);
let response = app
.authed_api_request("/api/desktop/plan", |client, url| client.get(url))
.await
.map_err(|e| {
println!("Failed to fetch plan: {e}");
e.to_string()
})?;
println!("Plan fetch response status: {}", response.status());

if !response.status().is_success() {
let error_msg = format!("Failed to fetch plan: {}", response.status());
return Err(error_msg);
}

#[derive(Deserialize)]
struct Response {
upgraded: bool,
intercom_hash: Option<String>,
}

let plan_response: Response = response.json().await.map_err(|e| e.to_string())?;

auth.plan = Some(Plan {
upgraded: plan_response.upgraded,
last_checked: chrono::Utc::now().timestamp() as i32,
manual: auth.plan.as_ref().is_some_and(|p| p.manual),
});
auth.intercom_hash = Some(plan_response.intercom_hash.unwrap_or_default());
auth.organizations = api::fetch_organizations(app)
// Commented out plan fetch - not implemented yet
// println!(
// "Fetching plan for user {}",
// auth.user_id.as_deref().unwrap_or("unknown")
// );
// let response = app
// .authed_api_request("/api/desktop/plan", |client, url| client.get(url))
// .await
// .map_err(|e| {
// println!("Failed to fetch plan: {e}");
// e.to_string()
// })?;
// println!("Plan fetch response status: {}", response.status());

// if !response.status().is_success() {
// let error_msg = format!("Failed to fetch plan: {}", response.status());
// return Err(error_msg);
// }

// #[derive(Deserialize)]
// struct Response {
// upgraded: bool,
// intercom_hash: Option<String>,
// }

// let plan_response: Response = response.json().await.map_err(|e| e.to_string())?;

// auth.plan = Some(Plan {
// upgraded: plan_response.upgraded,
// last_checked: chrono::Utc::now().timestamp() as i32,
// manual: auth.plan.as_ref().is_some_and(|p| p.manual),
// });
// auth.intercom_hash = Some(plan_response.intercom_hash.unwrap_or_default());

// Commented out organizations fetch - not implemented
// auth.organizations = api::fetch_organizations(app)
// .await
// .map_err(|e| e.to_string())?;
auth.workspaces = api::fetch_workspaces(app)
.await
.map_err(|e| e.to_string())?;

Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src-tauri/src/deeplink_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ impl DeepLinkAction {
capture_target,
capture_system_audio,
organization_id: None,
workspace_id: None,
};

crate::recording::start_recording(app.clone(), state, inputs)
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,7 @@ async fn upload_exported_video(
mode: UploadMode,
channel: Channel<UploadProgress>,
organization_id: Option<String>,
workspace_id: Option<String>,
) -> Result<UploadResult, String> {
let Ok(Some(auth)) = AuthStore::get(&app) else {
AuthStore::set(&app, None).map_err(|e| e.to_string())?;
Expand Down Expand Up @@ -1168,6 +1169,7 @@ async fn upload_exported_video(
Some(meta.pretty_name.clone()),
Some(metadata.clone()),
organization_id,
workspace_id,
)
.await
}
Expand Down Expand Up @@ -1569,6 +1571,7 @@ async fn check_upgraded_and_update(app: AppHandle) -> Result<bool, String> {
last_checked: chrono::Utc::now().timestamp() as i32,
}),
organizations: auth.organizations,
workspaces: auth.workspaces,
};
println!("Updating auth store with new pro status");
AuthStore::set(&app, Some(updated_auth)).map_err(|e| e.to_string())?;
Expand Down Expand Up @@ -2245,6 +2248,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) {
mode: event.mode,
capture_system_audio: settings.system_audio,
organization_id: settings.organization_id,
workspace_id: settings.workspace_id,
}
})
.await;
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop/src-tauri/src/recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ pub struct StartRecordingInputs {
pub mode: RecordingMode,
#[serde(default)]
pub organization_id: Option<String>,
#[serde(default)]
pub workspace_id: Option<String>,
}

#[derive(tauri_specta::Event, specta::Type, Clone, Debug, serde::Serialize)]
Expand Down Expand Up @@ -338,6 +340,7 @@ pub async fn start_recording(
)),
None,
inputs.organization_id.clone(),
inputs.workspace_id.clone(),
)
.await
{
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src-tauri/src/recording_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub struct RecordingSettingsStore {
pub mode: Option<RecordingMode>,
pub system_audio: bool,
pub organization_id: Option<String>,
pub workspace_id: Option<String>,
}

impl RecordingSettingsStore {
Expand Down
7 changes: 6 additions & 1 deletion apps/desktop/src-tauri/src/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ pub async fn upload_image(
.ok_or("Invalid file path")?
.to_string();

let s3_config = create_or_get_video(app, true, None, None, None, None).await?;
let s3_config = create_or_get_video(app, true, None, None, None, None, None).await?;

let (stream, total_size) = file_reader_stream(file_path).await?;
singlepart_uploader(
Expand Down Expand Up @@ -207,6 +207,7 @@ pub async fn create_or_get_video(
name: Option<String>,
meta: Option<S3VideoMeta>,
organization_id: Option<String>,
workspace_id: Option<String>,
) -> Result<S3UploadMeta, AuthedApiError> {
let mut s3_config_url = if let Some(id) = video_id {
format!("/api/desktop/video/create?recordingMode=desktopMP4&videoId={id}")
Expand All @@ -233,6 +234,10 @@ pub async fn create_or_get_video(
s3_config_url.push_str(&format!("&orgId={}", org_id));
}

if let Some(ws_id) = workspace_id {
s3_config_url.push_str(&format!("&workspaceId={}", ws_id));
}

let response = app
.authed_api_request(s3_config_url, |client, url| client.get(url))
.await?;
Expand Down
58 changes: 58 additions & 0 deletions apps/desktop/src/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,61 @@ export const Flip = (props: { class: string }) => {
</svg>
);
};

export const DoubleArrowSwitcher = (props: { class: string }) => {
return (
<svg
class={props.class}
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M10.9521 9.11328C11.1032 8.96224 11.3479 8.96236 11.499 9.11328C11.6502 9.26445 11.6502 9.50996 11.499 9.66113L8.27344 12.8867C8.12234 13.0376 7.87768 13.0376 7.72656 12.8867L4.5 9.66113C4.34891 9.51003 4.34905 9.26447 4.5 9.11328C4.65117 8.96211 4.89668 8.96212 5.04785 9.11328L8 12.0654L10.9521 9.11328ZM7.72656 3.11328C7.87771 2.96232 8.12231 2.96229 8.27344 3.11328L11.499 6.33887C11.6502 6.49004 11.6502 6.73555 11.499 6.88672C11.3479 7.03752 11.1032 7.03764 10.9521 6.88672L8 3.93457L5.04785 6.88672C4.89667 7.03779 4.65114 7.03786 4.5 6.88672C4.34899 6.73557 4.34897 6.49001 4.5 6.33887L7.72656 3.11328Z"
fill="currentColor"
/>
</svg>
);
};

export const ArrowUpRight = (props: { class: string }) => {
return (
<svg
class={props.class}
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M11.9996 3.40039C12.0388 3.40037 12.0782 3.40352 12.1168 3.41113C12.14 3.41572 12.162 3.42446 12.1842 3.43164C12.1987 3.43635 12.2139 3.43946 12.2281 3.44531C12.2513 3.45485 12.2729 3.46725 12.2945 3.47949C12.3069 3.48651 12.3196 3.493 12.3316 3.50098C12.3649 3.52313 12.3962 3.548 12.4244 3.57617L12.5015 3.66992C12.5117 3.68534 12.5183 3.70266 12.5269 3.71875C12.5363 3.73619 12.5467 3.75307 12.5543 3.77148C12.5844 3.84455 12.6001 3.92218 12.6002 4V10.5C12.6002 10.8314 12.331 11.1006 11.9996 11.1006C11.6684 11.1004 11.4 10.8312 11.4 10.5V5.44922L4.42439 12.4248C4.19011 12.6591 3.81008 12.659 3.57576 12.4248C3.34145 12.1905 3.34145 11.8105 3.57576 11.5762L10.5513 4.60059H5.49959C5.1684 4.60038 4.89998 4.33124 4.89998 4C4.90019 3.66894 5.16853 3.4006 5.49959 3.40039H11.9996Z"
fill="currentColor"
/>
</svg>
);
};

export const RecordFill = (props: { class: string }) => {
return (
<svg
class={props.class}
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
opacity="0.6"
d="M14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14V15C4.13401 15 1 11.866 1 8C1 4.13401 4.13401 1 8 1C11.866 1 15 4.13401 15 8C15 11.866 11.866 15 8 15V14C11.3137 14 14 11.3137 14 8Z"
fill="currentColor"
/>
<path d="M11 8C11 9.65685 9.65685 11 8 11C6.34315 11 5 9.65685 5 8C5 6.34315 6.34315 5 8 5C9.65685 5 11 6.34315 11 8Z" fill="currentColor" />
</svg>
);
};
Loading