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
158 changes: 112 additions & 46 deletions airborne_dashboard/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,21 @@ export default function DashboardHome() {
const [reqOrgName, setReqOrgName] = useState("");
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [phoneNumber, setPhoneNumber] = useState("");
const [appStoreLink, setAppStoreLink] = useState("");
const [playStoreLink, setPlayStoreLink] = useState("");
const [orgRequestSuccess, setOrgRequestSuccess] = useState(false);
const { data: orgs, mutate: refreshOrgs } = useSWR(token ? "/organisations" : null, (url) =>
apiFetch<OrganisationsList>(url, {}, { token, logout })
);

const resetOrgRequestForm = () => {
setReqOrgName("");
setName("");
setEmail("");
setAppStoreLink("");
setPlayStoreLink("");
setOrgRequestSuccess(false);
};
const orgList: { name: string; applications: { application: string }[] }[] = orgs?.organisations || [];
const apps = useMemo(
() => orgList.find((o) => o.name === org)?.applications?.map((a) => a.application) || [],
Expand All @@ -36,6 +45,24 @@ export default function DashboardHome() {
const [orgName, setOrgName] = useState("");
const [appName, setAppName] = useState("");

// Check for saved org request data on component mount
useEffect(() => {
const savedRequest = localStorage.getItem("org_request_data");
if (savedRequest && config?.organisation_creation_disabled) {
try {
const requestData = JSON.parse(savedRequest);
setReqOrgName(requestData.organisation_name || "");
setName(requestData.name || "");
setEmail(requestData.email || "");
setAppStoreLink(requestData.app_store_link || "");
setPlayStoreLink(requestData.play_store_link || "");
setOrgRequestSuccess(true);
} catch (err) {
console.error("Error parsing saved org request:", err);
}
}
}, [config?.organisation_creation_disabled]);

useEffect(() => {
if (orgList.length === 0) {
// No orgs → prompt to create
Expand Down Expand Up @@ -82,7 +109,6 @@ export default function DashboardHome() {
organisation_name: reqOrgName,
name,
email,
phone: phoneNumber,
app_store_link: appStoreLink,
play_store_link: playStoreLink,
},
Expand All @@ -91,6 +117,20 @@ export default function DashboardHome() {
token,
}
);

// Save request data to local storage
const requestData = {
organisation_name: reqOrgName,
name,
email,
app_store_link: appStoreLink,
play_store_link: playStoreLink,
requested_at: new Date().toISOString(),
};
localStorage.setItem("org_request_data", JSON.stringify(requestData));
Comment thread Fixed

// Show success message
setOrgRequestSuccess(true);
} catch (err) {
console.error("Error while requesting organisation:", err);
}
Expand All @@ -101,56 +141,82 @@ export default function DashboardHome() {
return (
<div className="mt-10">
{config?.organisation_creation_disabled ? (
<Card className="mx-auto max-w-lg shadow-lg">
<CardHeader>
<CardTitle className="text-2xl font-semibold">Request your first Organisation</CardTitle>
<CardDescription>
You need an organisation to get started. Fill in the details below to request one.
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="orgName">Organisation name</Label>
<Input id="orgName" value={reqOrgName} onChange={(e) => setReqOrgName(e.target.value)} />
orgRequestSuccess ? (
<Card className="mx-auto max-w-lg shadow-lg border-green-200 bg-green-50">
<CardHeader>
<CardTitle className="text-2xl font-semibold text-green-800">Request Submitted Successfully!</CardTitle>
<CardDescription className="text-green-700">
Your organisation request has been submitted.
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3 text-green-800">
<p className="font-medium">Organisation: {reqOrgName}</p>
<p>Someone from our team will connect with you soon to process your request.</p>
<p className="text-sm text-green-600">We&apos;ll reach out to you at {email}.</p>
</div>
</CardContent>
<CardFooter>
<Button
variant="outline"
className="w-full border-green-300 text-green-800 hover:bg-green-100"
onClick={resetOrgRequestForm}
>
Submit Another Request
</Button>
</CardFooter>
</Card>
) : (
<Card className="mx-auto max-w-lg shadow-lg">
<CardHeader>
<CardTitle className="text-2xl font-semibold">Request your first Organisation</CardTitle>
<CardDescription>
You need an organisation to get started. Fill in the details below to request one.
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="orgName">Organisation name</Label>
<Input id="orgName" value={reqOrgName} onChange={(e) => setReqOrgName(e.target.value)} />
</div>

<div className="space-y-2">
<Label htmlFor="name">Your name</Label>
<Input id="name" value={name} onChange={(e) => setName(e.target.value)} />
</div>
<div className="space-y-2">
<Label htmlFor="name">Your name</Label>
<Input id="name" value={name} onChange={(e) => setName(e.target.value)} />
</div>

<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
</div>

<div className="space-y-2">
<Label htmlFor="phoneNumber">Phone Number</Label>
<Input id="phoneNumber" value={phoneNumber} onChange={(e) => setPhoneNumber(e.target.value)} />
</div>

<div className="space-y-2">
<Label htmlFor="appStoreLink">App Store Link</Label>
<Input id="appStoreLink" value={appStoreLink} onChange={(e) => setAppStoreLink(e.target.value)} />
</div>
<div className="space-y-2">
<Label htmlFor="appStoreLink">App Store Link</Label>
<Input id="appStoreLink" value={appStoreLink} onChange={(e) => setAppStoreLink(e.target.value)} />
</div>

<div className="space-y-2">
<Label htmlFor="playStoreLink">Play Store Link</Label>
<Input id="playStoreLink" value={playStoreLink} onChange={(e) => setPlayStoreLink(e.target.value)} />
<div className="space-y-2">
<Label htmlFor="playStoreLink">Play Store Link</Label>
<Input
id="playStoreLink"
value={playStoreLink}
onChange={(e) => setPlayStoreLink(e.target.value)}
/>
</div>
</div>
</div>
</CardContent>
<CardFooter>
<Button
className="w-full"
onClick={onRequestOrg}
disabled={!reqOrgName.trim() || !name.trim() || !email.trim() || !phoneNumber.trim()}
>
Request Organisation
</Button>
</CardFooter>
</Card>
</CardContent>
<CardFooter>
<Button
className="w-full"
onClick={onRequestOrg}
disabled={!reqOrgName.trim() || !name.trim() || !email.trim()}
>
Request Organisation
</Button>
</CardFooter>
</Card>
)
) : (
<Card className="mx-auto max-w-lg shadow-lg">
<CardHeader>
Expand Down
1 change: 1 addition & 0 deletions airborne_dashboard/providers/app-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
setUser(null);
setOrg(null);
setApp(null);
if (localStorage.getItem("org_request_data")) localStorage.removeItem("org_request_data");
// redirect to login after clearing state
if (typeof window !== "undefined") window.location.href = "/login";
};
Expand Down
31 changes: 21 additions & 10 deletions airborne_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,7 @@ async fn main() -> std::io::Result<()> {
.ok()
.and_then(|v| v.parse::<bool>().ok())
.unwrap_or_default();
let gcp_service_account_json_path = if organisation_creation_disabled {
std::env::var("GCP_SERVICE_ACCOUNT_PATH")
.expect("GCP_SERVICE_ACCOUNT_PATH must be set if ORGANISATION_CREATION_DISABLED=true")
} else {
"".to_string()
};

let spreadsheet_id = if organisation_creation_disabled {
std::env::var("GOOGLE_SPREADSHEET_ID")
.expect("GOOGLE_SPREADSHEET_ID must be set if ORGANISATION_CREATION_DISABLED=true")
Expand Down Expand Up @@ -138,6 +133,25 @@ async fn main() -> std::io::Result<()> {
.expect("Failed to run pending migrations");
}

let gsa_creds: Option<yup_oauth2::ServiceAccountKey> = if organisation_creation_disabled {
let creds_from_path = if let Ok(path) = std::env::var("GCP_SERVICE_ACCOUNT_PATH") {
yup_oauth2::read_service_account_key(path).await.ok()
} else {
None
};

let creds_from_env = if let Ok(encrypted) = std::env::var("GOOGLE_SERVICE_ACCOUNT_KEY") {
let decrypted = decrypt_kms(&aws_kms_client, encrypted).await;
serde_json::from_str::<yup_oauth2::ServiceAccountKey>(&decrypted).ok()
} else {
None
};

creds_from_path.or(creds_from_env)
} else {
None
};

// Initialize DB pool
info!("Creating db pool");
let pool = db::establish_pool(&aws_kms_client).await;
Expand Down Expand Up @@ -176,11 +190,8 @@ async fn main() -> std::io::Result<()> {
rustls::crypto::ring::default_provider()
.install_default()
.expect("Failed to install rustls crypto provider");
let creds = yup_oauth2::read_service_account_key(gcp_service_account_json_path)
.await
.expect("Can't read gcp credential, an error occurred");

let gcp_auth = ServiceAccountAuthenticator::builder(creds)
let gcp_auth = ServiceAccountAuthenticator::builder(gsa_creds.expect("You need to have valid value for env GOOGLE_SERVICE_ACCOUNT_KEY or GCP_SERVICE_ACCOUNT_PATH if ORGANISATION_CREATION_DISABLED=true"))
.build()
.await
.expect("There was an error, trying to build connection with gcp authenticator");
Expand Down
4 changes: 1 addition & 3 deletions airborne_server/src/organisation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ pub struct OrganisationRequest {
pub organisation_name: String,
pub name: String,
pub email: String,
pub phone: Option<String>,
pub play_store_link: Option<String>,
pub app_store_link: Option<String>,
}
Expand All @@ -87,7 +86,6 @@ async fn request_organisation(
let organisation_name = body.organisation_name.clone();
let name = body.name.clone();
let email = body.email.clone();
let phone = body.phone.clone().unwrap_or("".to_string());
let play_store_link = body.play_store_link.clone().unwrap_or("".to_string());
let app_store_link = body.app_store_link.clone().unwrap_or("".to_string());

Expand Down Expand Up @@ -133,7 +131,7 @@ async fn request_organisation(
values: Some(vec![vec![
serde_json::Value::String(name),
serde_json::Value::String(email),
serde_json::Value::String(phone),
serde_json::Value::String("".to_string()), // phone number
serde_json::Value::String(organisation_name.clone()),
serde_json::Value::String(app_store_link),
serde_json::Value::String(play_store_link),
Expand Down
Loading