diff --git a/builder-api/src/main/java/org/acme/api/error/JsonServerExceptionMappers.java b/builder-api/src/main/java/org/acme/api/error/JsonServerExceptionMappers.java index abf0f4d0..ffaf9e44 100644 --- a/builder-api/src/main/java/org/acme/api/error/JsonServerExceptionMappers.java +++ b/builder-api/src/main/java/org/acme/api/error/JsonServerExceptionMappers.java @@ -15,15 +15,13 @@ public class JsonServerExceptionMappers { public Response map(MismatchedInputException e) { Log.warn(e); // e.g. screenerName is object but DTO expects String - String field = - e.getPath() != null && !e.getPath().isEmpty() - ? e.getPath().get(e.getPath().size() - 1).getFieldName() - : "request body"; + String field = e.getPath() != null && !e.getPath().isEmpty() + ? e.getPath().get(e.getPath().size() - 1).getFieldName() + : "request body"; return Response.status(Response.Status.BAD_REQUEST) .type(MediaType.APPLICATION_JSON) - .entity(ApiError.of("Invalid type for field '" + field + "'.")) - .build(); + .entity(ApiError.of("Invalid type for field '" + field + "'.")).build(); } @ServerExceptionMapper @@ -32,16 +30,16 @@ public Response map(JsonParseException e) { // malformed JSON like { "schema": } return Response.status(Response.Status.BAD_REQUEST) .type(MediaType.APPLICATION_JSON) - .entity(ApiError.of("Malformed JSON.")) - .build(); + .entity(ApiError.of("JsonParseException: Malformed JSON.")).build(); } @ServerExceptionMapper public Response map(WebApplicationException e) { + Log.info("Some malformed JSON"); Log.warn(e); return Response.status(Response.Status.BAD_REQUEST) .type(MediaType.APPLICATION_JSON) - .entity(ApiError.of("Malformed JSON.")) + .entity(ApiError.of("WebApplicationException: Malformed JSON.")) .build(); } @@ -51,7 +49,6 @@ public Response map(JsonMappingException e) { // other mapping errors return Response.status(Response.Status.BAD_REQUEST) .type(MediaType.APPLICATION_JSON) - .entity(ApiError.of("Invalid request body.")) - .build(); + .entity(ApiError.of("Invalid request body.")).build(); } } diff --git a/builder-api/src/main/java/org/acme/controller/AccountResource.java b/builder-api/src/main/java/org/acme/controller/AccountResource.java new file mode 100644 index 00000000..472ff4a4 --- /dev/null +++ b/builder-api/src/main/java/org/acme/controller/AccountResource.java @@ -0,0 +1,64 @@ +package org.acme.controller; + +import io.quarkus.security.identity.SecurityIdentity; +import jakarta.inject.Inject; +import jakarta.validation.Validator; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.*; + +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.acme.api.error.ApiError; +import org.acme.auth.AuthUtils; +import org.acme.enums.AccountHookAction; +import org.acme.functions.AccountHooks; +import org.acme.model.dto.Auth.AccountHookRequest; +import org.acme.model.dto.Auth.AccountHookResponse; + +@Path("/api") +public class AccountResource { + + @Inject + Validator validator; + + @Inject + AccountHooks accountHooks; + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Path("/account-hooks") + public Response accountHooks(@Context SecurityIdentity identity, + AccountHookRequest request) { + + Set hooks = request.hooks(); + String userId = AuthUtils.getUserId(identity); + + if (userId == null) { + return Response.status(Response.Status.UNAUTHORIZED) + .entity(new ApiError(true, "Unauthorized.")).build(); + } + + // Map of AccountHookAction to a hook side-effect function + // The function returns whether the side-effect was successful + Map> hooksMap = Map.of( + AccountHookAction.ADD_EXAMPLE_SCREENER, + accountHooks::addExampleScreenerToAccount, + AccountHookAction.UNABLE_TO_DETERMINE, + (String uId) -> true); + + // Run each action's function and determine whether successful + Map hookResults = hooks.stream() + .collect(Collectors.toMap(s -> s.toString(), s -> { + Predicate fn = hooksMap.get(s); + return fn.test(userId); + })); + + AccountHookResponse responseBody = new AccountHookResponse(true, + hookResults); + + return Response.ok(responseBody).build(); + } +} diff --git a/builder-api/src/main/java/org/acme/enums/AccountHookAction.java b/builder-api/src/main/java/org/acme/enums/AccountHookAction.java new file mode 100644 index 00000000..153bd1a9 --- /dev/null +++ b/builder-api/src/main/java/org/acme/enums/AccountHookAction.java @@ -0,0 +1,30 @@ +package org.acme.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public enum AccountHookAction { + ADD_EXAMPLE_SCREENER("add example screener"), + UNABLE_TO_DETERMINE("UNABLE_TO_DETERMINE"); + + private final String label; + + AccountHookAction(String label) { + this.label = label; + } + + @JsonValue + public String getLabel() { + return label; + } + + @JsonCreator + public static AccountHookAction fromValue(String value) { + for (AccountHookAction action : values()) { + if (action.label.equalsIgnoreCase(value)) { + return action; + } + } + return UNABLE_TO_DETERMINE; + } +} \ No newline at end of file diff --git a/builder-api/src/main/java/org/acme/functions/AccountHooks.java b/builder-api/src/main/java/org/acme/functions/AccountHooks.java new file mode 100644 index 00000000..b91b166e --- /dev/null +++ b/builder-api/src/main/java/org/acme/functions/AccountHooks.java @@ -0,0 +1,28 @@ +package org.acme.functions; + +import io.quarkus.logging.Log; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.acme.service.ExampleScreenerImportService; + +@ApplicationScoped +public class AccountHooks { + @Inject + ExampleScreenerImportService exampleScreenerImportService; + + public Boolean addExampleScreenerToAccount(String userId) { + try { + Log.info("Running ADD_EXAMPLE_SCREENER hook for user: " + userId); + String screenerId = exampleScreenerImportService.importForUser(userId); + Log.info("Imported example screener " + screenerId + " for user " + userId); + return true; + } catch (Exception e) { + Log.error( + "Failed to run ADD_EXAMPLE_SCREENER hook for user: " + + userId, + e); + return false; + } + } +} diff --git a/builder-api/src/main/java/org/acme/model/dto/Auth/AccountHookRequest.java b/builder-api/src/main/java/org/acme/model/dto/Auth/AccountHookRequest.java new file mode 100644 index 00000000..158c4821 --- /dev/null +++ b/builder-api/src/main/java/org/acme/model/dto/Auth/AccountHookRequest.java @@ -0,0 +1,8 @@ +package org.acme.model.dto.Auth; + +import java.util.Set; + +import org.acme.enums.AccountHookAction; + +public record AccountHookRequest(Set hooks) { +} \ No newline at end of file diff --git a/builder-api/src/main/java/org/acme/model/dto/Auth/AccountHookResponse.java b/builder-api/src/main/java/org/acme/model/dto/Auth/AccountHookResponse.java new file mode 100644 index 00000000..8585daf7 --- /dev/null +++ b/builder-api/src/main/java/org/acme/model/dto/Auth/AccountHookResponse.java @@ -0,0 +1,7 @@ +package org.acme.model.dto.Auth; + +import java.util.Map; + +public record AccountHookResponse(Boolean success, + Map actions) { +}; diff --git a/builder-api/src/main/java/org/acme/service/ExampleScreenerImportService.java b/builder-api/src/main/java/org/acme/service/ExampleScreenerImportService.java new file mode 100644 index 00000000..8508ed3f --- /dev/null +++ b/builder-api/src/main/java/org/acme/service/ExampleScreenerImportService.java @@ -0,0 +1,425 @@ +package org.acme.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.logging.Log; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.acme.model.domain.Benefit; +import org.acme.model.domain.BenefitDetail; +import org.acme.model.domain.CheckConfig; +import org.acme.model.domain.EligibilityCheck; +import org.acme.model.domain.Screener; +import org.acme.persistence.EligibilityCheckRepository; +import org.acme.persistence.ScreenerRepository; +import org.acme.persistence.StorageService; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +@ApplicationScoped +public class ExampleScreenerImportService { + + private final ScreenerRepository screenerRepository; + private final EligibilityCheckRepository eligibilityCheckRepository; + private final StorageService storageService; + private final Optional configuredSeedPath; + private final ObjectMapper objectMapper; + + @Inject + public ExampleScreenerImportService( + ScreenerRepository screenerRepository, + EligibilityCheckRepository eligibilityCheckRepository, + StorageService storageService, + @ConfigProperty(name = "example-screener.seed-path") Optional configuredSeedPath + ) { + this.screenerRepository = screenerRepository; + this.eligibilityCheckRepository = eligibilityCheckRepository; + this.storageService = storageService; + this.configuredSeedPath = configuredSeedPath; + this.objectMapper = new ObjectMapper(); + } + + public String importForUser(String userId) throws Exception { + Path seedRoot = resolveSeedRoot(); + SeedData seedData = loadSeedData(seedRoot); + + Map importedCustomCheckIds = importReferencedCustomChecks(seedData, userId); + + List importedBenefits = new ArrayList<>(); + List importedBenefitDetails = new ArrayList<>(); + for (Benefit seedBenefit : seedData.benefits()) { + Benefit importedBenefit = cloneBenefit(seedBenefit, userId, importedCustomCheckIds); + importedBenefits.add(importedBenefit); + importedBenefitDetails.add(new BenefitDetail( + importedBenefit.getId(), + importedBenefit.getName(), + importedBenefit.getDescription() + )); + } + + Screener importedScreener = new Screener(); + importedScreener.setOwnerId(userId); + importedScreener.setScreenerName(seedData.screener().getScreenerName()); + importedScreener.setBenefits(importedBenefitDetails); + + String newScreenerId = screenerRepository.saveNewWorkingScreener(importedScreener); + importedScreener.setId(newScreenerId); + + for (Benefit importedBenefit : importedBenefits) { + screenerRepository.saveNewCustomBenefit(newScreenerId, importedBenefit); + } + + String formPath = storageService.getScreenerWorkingFormSchemaPath(newScreenerId); + storageService.writeJsonToStorage(formPath, seedData.formSchema()); + + Log.info("Imported example screener " + newScreenerId + " for user " + userId); + return newScreenerId; + } + + private Map importReferencedCustomChecks(SeedData seedData, String userId) throws Exception { + Set referencedCustomCheckIds = new LinkedHashSet<>(); + for (Benefit benefit : seedData.benefits()) { + if (benefit.getChecks() == null) { + continue; + } + for (CheckConfig checkConfig : benefit.getChecks()) { + String sourceCheckId = resolveSourceCheckId(checkConfig); + if (sourceCheckId != null && !isLibraryCheckId(sourceCheckId)) { + referencedCustomCheckIds.add(sourceCheckId); + } + } + } + + Map remappedCheckIds = new HashMap<>(); + for (String seedSourceCheckId : referencedCustomCheckIds) { + SeedCustomCheckVersions seedCustomCheckVersions = findSeedCustomCheckVersions(seedData, seedSourceCheckId); + + if (seedCustomCheckVersions.workingCheck() != null) { + String newWorkingId = upsertWorkingCustomCheck( + userId, + seedCustomCheckVersions.workingCheck(), + seedData.dmnByCheckId() + ); + remappedCheckIds.put(seedCustomCheckVersions.workingCheck().getId(), newWorkingId); + } + + if (seedCustomCheckVersions.publishedCheck() != null) { + String newPublishedId = upsertPublishedCustomCheck( + userId, + seedCustomCheckVersions.publishedCheck(), + seedData.dmnByCheckId() + ); + remappedCheckIds.put(seedCustomCheckVersions.publishedCheck().getId(), newPublishedId); + } + + if (!remappedCheckIds.containsKey(seedSourceCheckId)) { + throw new IllegalStateException("No imported check mapping found for seed check " + seedSourceCheckId); + } + } + + return remappedCheckIds; + } + + private SeedCustomCheckVersions findSeedCustomCheckVersions(SeedData seedData, String seedSourceCheckId) { + EligibilityCheck referencedCheck = seedData.workingCustomChecks().get(seedSourceCheckId); + if (referencedCheck == null) { + referencedCheck = seedData.publishedCustomChecks().get(seedSourceCheckId); + } + if (referencedCheck == null) { + throw new IllegalStateException("Missing seed custom check for referenced id " + seedSourceCheckId); + } + + String seedWorkingId = buildWorkingCheckId( + referencedCheck.getOwnerId(), + referencedCheck.getModule(), + referencedCheck.getName() + ); + String seedPublishedId = buildPublishedCheckId( + referencedCheck.getOwnerId(), + referencedCheck.getModule(), + referencedCheck.getName(), + referencedCheck.getVersion() + ); + + return new SeedCustomCheckVersions( + seedData.workingCustomChecks().get(seedWorkingId), + seedData.publishedCustomChecks().get(seedPublishedId) + ); + } + + private String upsertWorkingCustomCheck( + String userId, + EligibilityCheck seedCheck, + Map dmnByCheckId + ) throws Exception { + EligibilityCheck importedCheck = cloneEligibilityCheck(seedCheck); + importedCheck.setOwnerId(userId); + importedCheck.setIsArchived(false); + + String newWorkingId = buildWorkingCheckId(userId, importedCheck.getModule(), importedCheck.getName()); + importedCheck.setId(newWorkingId); + + if (eligibilityCheckRepository.getWorkingCustomCheck(userId, newWorkingId, true).isPresent()) { + eligibilityCheckRepository.updateWorkingCustomCheck(importedCheck); + } else { + eligibilityCheckRepository.saveNewWorkingCustomCheck(importedCheck); + } + + writeCheckDmn(newWorkingId, seedCheck, dmnByCheckId); + return newWorkingId; + } + + private String upsertPublishedCustomCheck( + String userId, + EligibilityCheck seedCheck, + Map dmnByCheckId + ) throws Exception { + EligibilityCheck importedCheck = cloneEligibilityCheck(seedCheck); + importedCheck.setOwnerId(userId); + importedCheck.setIsArchived(false); + + String newPublishedId = buildPublishedCheckId( + userId, + importedCheck.getModule(), + importedCheck.getName(), + importedCheck.getVersion() + ); + importedCheck.setId(newPublishedId); + + if (eligibilityCheckRepository.getPublishedCustomCheck(userId, newPublishedId).isPresent()) { + eligibilityCheckRepository.updatePublishedCustomCheck(importedCheck); + } else { + try { + eligibilityCheckRepository.saveNewPublishedCustomCheck(importedCheck); + } catch (Exception e) { + eligibilityCheckRepository.updatePublishedCustomCheck(importedCheck); + } + } + + writeCheckDmn(newPublishedId, seedCheck, dmnByCheckId); + return newPublishedId; + } + + private void writeCheckDmn(String newCheckId, EligibilityCheck seedCheck, Map dmnByCheckId) throws Exception { + String dmnModel = dmnByCheckId.get(seedCheck.getId()); + if ((dmnModel == null || dmnModel.isBlank()) && seedCheck.getDmnModel() != null) { + dmnModel = seedCheck.getDmnModel(); + } + if (dmnModel == null || dmnModel.isBlank()) { + throw new IllegalStateException("Missing DMN model for seed check " + seedCheck.getId()); + } + + storageService.writeStringToStorage( + storageService.getCheckDmnModelPath(newCheckId), + dmnModel, + "application/xml" + ); + } + + private Benefit cloneBenefit(Benefit seedBenefit, String userId, Map importedCustomCheckIds) { + Benefit importedBenefit = objectMapper.convertValue(seedBenefit, Benefit.class); + importedBenefit.setId(UUID.randomUUID().toString()); + importedBenefit.setOwnerId(userId); + importedBenefit.setChecks(remapCheckConfigs(seedBenefit.getChecks(), importedCustomCheckIds)); + return importedBenefit; + } + + private List remapCheckConfigs(List seedChecks, Map importedCustomCheckIds) { + if (seedChecks == null || seedChecks.isEmpty()) { + return Collections.emptyList(); + } + + List importedChecks = new ArrayList<>(); + for (CheckConfig seedCheck : seedChecks) { + CheckConfig importedCheck = objectMapper.convertValue(seedCheck, CheckConfig.class); + importedCheck.setCheckId(UUID.randomUUID().toString()); + + String sourceCheckId = resolveSourceCheckId(seedCheck); + if (sourceCheckId != null) { + if (isLibraryCheckId(sourceCheckId)) { + importedCheck.setSourceCheckId(sourceCheckId); + } else { + String remappedSourceCheckId = importedCustomCheckIds.get(sourceCheckId); + if (remappedSourceCheckId == null) { + throw new IllegalStateException("Missing imported custom check id for " + sourceCheckId); + } + importedCheck.setSourceCheckId(remappedSourceCheckId); + } + } + + importedChecks.add(importedCheck); + } + + return importedChecks; + } + + private EligibilityCheck cloneEligibilityCheck(EligibilityCheck seedCheck) { + return objectMapper.convertValue(seedCheck, EligibilityCheck.class); + } + + private String resolveSourceCheckId(CheckConfig checkConfig) { + if (checkConfig.getSourceCheckId() != null && !checkConfig.getSourceCheckId().isBlank()) { + return checkConfig.getSourceCheckId(); + } + return checkConfig.getCheckId(); + } + + private boolean isLibraryCheckId(String checkId) { + return checkId != null && checkId.startsWith("L"); + } + + private SeedData loadSeedData(Path seedRoot) throws IOException { + Path workingScreenersDir = seedRoot.resolve("firestore").resolve("workingScreener"); + List screenerFiles = listJsonFiles(workingScreenersDir); + if (screenerFiles.size() != 1) { + throw new IllegalStateException("Expected exactly one working screener seed document, found " + screenerFiles.size()); + } + + Path screenerFile = screenerFiles.get(0); + Screener screener = readJsonFile(screenerFile, Screener.class); + String screenerDocId = stripExtension(screenerFile.getFileName().toString()); + + Path benefitsDir = workingScreenersDir.resolve(screenerDocId).resolve("customBenefit"); + List benefits = new ArrayList<>(); + for (Path benefitFile : listJsonFiles(benefitsDir)) { + benefits.add(readJsonFile(benefitFile, Benefit.class)); + } + + JsonNode formSchema = objectMapper.readTree( + Files.readString(seedRoot.resolve("storage").resolve("form").resolve("working").resolve(screenerDocId + ".json")) + ); + + return new SeedData( + screener, + benefits, + formSchema, + loadChecks(seedRoot.resolve("firestore").resolve("workingCustomCheck")), + loadChecks(seedRoot.resolve("firestore").resolve("publishedCustomCheck")), + loadDmnFiles(seedRoot.resolve("storage").resolve("check")) + ); + } + + private Map loadChecks(Path checksDir) throws IOException { + Map checksById = new LinkedHashMap<>(); + if (!Files.isDirectory(checksDir)) { + return checksById; + } + + for (Path checkFile : listJsonFiles(checksDir)) { + EligibilityCheck check = readJsonFile(checkFile, EligibilityCheck.class); + checksById.put(check.getId(), check); + } + return checksById; + } + + private Map loadDmnFiles(Path dmnDir) throws IOException { + Map dmnByCheckId = new HashMap<>(); + if (!Files.isDirectory(dmnDir)) { + return dmnByCheckId; + } + + try (var stream = Files.list(dmnDir)) { + stream + .filter(Files::isRegularFile) + .filter(path -> path.getFileName().toString().endsWith(".dmn")) + .sorted(Comparator.comparing(path -> path.getFileName().toString())) + .forEach(path -> { + try { + dmnByCheckId.put(stripExtension(path.getFileName().toString()), Files.readString(path)); + } catch (IOException e) { + throw new RuntimeException("Failed to read DMN file " + path, e); + } + }); + } catch (RuntimeException e) { + if (e.getCause() instanceof IOException ioException) { + throw ioException; + } + throw e; + } + + return dmnByCheckId; + } + + private T readJsonFile(Path path, Class clazz) throws IOException { + return objectMapper.readValue(Files.readString(path), clazz); + } + + private List listJsonFiles(Path directory) throws IOException { + if (!Files.isDirectory(directory)) { + return Collections.emptyList(); + } + + try (var stream = Files.list(directory)) { + return stream + .filter(Files::isRegularFile) + .filter(path -> path.getFileName().toString().endsWith(".json")) + .sorted(Comparator.comparing(path -> path.getFileName().toString())) + .toList(); + } + } + + private Path resolveSeedRoot() { + List candidates = new ArrayList<>(); + configuredSeedPath + .map(String::trim) + .filter(path -> !path.isBlank()) + .map(Paths::get) + .ifPresent(candidates::add); + candidates.add(Paths.get("seed-data", "example-screener")); + candidates.add(Paths.get("..", "seed-data", "example-screener")); + + for (Path candidate : candidates) { + Path absoluteCandidate = candidate.toAbsolutePath().normalize(); + if (Files.isDirectory(absoluteCandidate)) { + return absoluteCandidate; + } + } + + throw new IllegalStateException("Could not find example screener seed data in any expected location"); + } + + private String buildWorkingCheckId(String ownerId, String module, String name) { + return "W-" + ownerId + "-" + module + "-" + name; + } + + private String buildPublishedCheckId(String ownerId, String module, String name, String version) { + return "P-" + ownerId + "-" + module + "-" + name + "-" + version; + } + + private String stripExtension(String filename) { + int extensionIndex = filename.lastIndexOf('.'); + if (extensionIndex == -1) { + return filename; + } + return filename.substring(0, extensionIndex); + } + + private record SeedData( + Screener screener, + List benefits, + JsonNode formSchema, + Map workingCustomChecks, + Map publishedCustomChecks, + Map dmnByCheckId + ) {} + + private record SeedCustomCheckVersions( + EligibilityCheck workingCheck, + EligibilityCheck publishedCheck + ) {} +} diff --git a/builder-frontend/src/App.tsx b/builder-frontend/src/App.tsx index 0e06f32a..2b954b51 100644 --- a/builder-frontend/src/App.tsx +++ b/builder-frontend/src/App.tsx @@ -9,25 +9,19 @@ import Screener from "./components/screener/Screener"; import Loading from "./components/Loading"; import { Match, Switch } from "solid-js"; - const ProtectedRoute = (props) => { - const { user, isAuthLoading } = useAuth(); - - const userThing = () => { - console.log(user()) - return user(); - } + const { user, isAuthLoading, isProvisioningAccount } = useAuth(); // If user is logged in, render the requested component, otherwise redirect to login return ( - + - + - + @@ -39,11 +33,23 @@ function App() { <> - } /> - } /> - } /> - -
404 - Page Not Found
} /> + } + /> + } + /> + } + /> + +
404 - Page Not Found
} + /> ); } diff --git a/builder-frontend/src/api/account.ts b/builder-frontend/src/api/account.ts new file mode 100644 index 00000000..5c62f228 --- /dev/null +++ b/builder-frontend/src/api/account.ts @@ -0,0 +1,26 @@ +import { env } from "@/config/environment"; + +import { authPost } from "@/api/auth"; + +const apiUrl = env.apiUrl; + +export const getAccountHooks = async () => { + const accountHookUrl = new URL(`${apiUrl}/account-hooks`); + + const hooksToCall = ["add example screener"]; + + try { + const response = await authPost(accountHookUrl.toString(), { + hooks: hooksToCall, + }); + + if (!response.ok) { + throw new Error(`Account hooks failed with status: ${response.status}`); + } + const data = await response.json(); + return data; + } catch (error) { + console.error("Error calling account hooks:", error); + throw error; // rethrow so you can handle it in your component if needed + } +}; diff --git a/builder-frontend/src/components/homeScreen/HomeScreen.tsx b/builder-frontend/src/components/homeScreen/HomeScreen.tsx index 86c35384..e2c60622 100644 --- a/builder-frontend/src/components/homeScreen/HomeScreen.tsx +++ b/builder-frontend/src/components/homeScreen/HomeScreen.tsx @@ -5,7 +5,6 @@ import ProjectsList from "./ProjectsList"; import Header from "../Header/Header"; import BdtNavbar, { NavbarProps } from "@/components/shared/BdtNavbar"; -0; const HomeScreen = () => { const [screenMode, setScreenMode] = createSignal<"screeners" | "checks">( diff --git a/builder-frontend/src/context/AuthContext.jsx b/builder-frontend/src/context/AuthContext.tsx similarity index 51% rename from builder-frontend/src/context/AuthContext.jsx rename to builder-frontend/src/context/AuthContext.tsx index 3bca567f..fd84f8aa 100644 --- a/builder-frontend/src/context/AuthContext.jsx +++ b/builder-frontend/src/context/AuthContext.tsx @@ -15,6 +15,7 @@ import { } from "firebase/auth"; import { auth } from "../firebase/firebase"; +import { getAccountHooks } from "@/api/account"; const AuthContext = createContext(); const googleProvider = new GoogleAuthProvider(); @@ -22,6 +23,7 @@ const googleProvider = new GoogleAuthProvider(); export function AuthProvider(props) { const [user, setUser] = createSignal("loading"); const [isAuthLoading, setIsAuthLoading] = createSignal(true); + const [isProvisioningAccount, setIsProvisioningAccount] = createSignal(false); let unsubscribe; onMount(() => { @@ -41,12 +43,45 @@ export function AuthProvider(props) { }; const register = async (email, password) => { - return createUserWithEmailAndPassword(auth, email, password); + setIsProvisioningAccount(true); + return createUserWithEmailAndPassword(auth, email, password).then( + (userCredential) => { + getAccountHooks() + .then( + () => { + console.log("Successfully hooked the account."); + }, + (error) => { + console.log("Error hooking the account", error); + }, + ) + .finally(() => { + setIsProvisioningAccount(false); + }); + }, + ); }; const loginWithGoogle = async () => { try { - return signInWithPopup(auth, googleProvider); + return signInWithPopup(auth, googleProvider).then((userCredential) => { + const { operationType } = userCredential; + if (operationType === "link" || operationType === "reauthenticate") { + setIsProvisioningAccount(true); + getAccountHooks() + .then( + () => { + console.log("Successfully hooked the account."); + }, + (error) => { + console.log("Error hooking the account", error); + }, + ) + .finally(() => { + setIsProvisioningAccount(false); + }); + } + }); } catch (error) { console.error("Google sign-in error:", error.message); } @@ -58,7 +93,15 @@ export function AuthProvider(props) { return ( {props.children}