my accountspanel.java

package org.nsomatrix;

import org.nsomatrix.SupabaseStorageClient;
import org.nsomatrix.DashboardPanel;
import org.nsomatrix.SupabaseLoginPanel;

import javax.swing.*;
import java.awt.*;

public class AccountPanel extends JPanel {

    private final CardLayout cardLayout = new CardLayout();

    private final SupabaseLoginPanel loginPanel = new SupabaseLoginPanel();
    private final DashboardPanel dashboardPanel = new DashboardPanel();

    private String accessToken;
    private String refreshToken;
    private String userEmail;
    private String userId;

    private final SupabaseStorageClient storageClient = new SupabaseStorageClient();

    public AccountPanel() {
        setLayout(cardLayout);
        add(loginPanel, "LOGIN");
        add(dashboardPanel, "DASHBOARD");

        loginPanel.setLoginCallback(authResponse -> {
            this.accessToken = authResponse.access_token;
            this.refreshToken = authResponse.refresh_token;
            this.userEmail = authResponse.user.email;
            this.userId = authResponse.user.id;

            // Set tokens and user ID in storage client
            storageClient.setAccessToken(accessToken);
            storageClient.setUserId(userId);

            dashboardPanel.setUserEmail(userEmail);
            dashboardPanel.setStorageClient(storageClient);

            cardLayout.show(AccountPanel.this, "DASHBOARD");
        });

        cardLayout.show(this, "LOGIN");
    }

    public void logout() {
        accessToken = null;
        refreshToken = null;
        userEmail = null;
        userId = null;
        storageClient.setAccessToken(null);
        storageClient.setUserId(null);
        dashboardPanel.clearState();
        cardLayout.show(this, "LOGIN");
    }

    public boolean isLoggedIn() {
        return accessToken != null;
    }

    public String getAccessToken() {
        return accessToken;
    }
}

my dashboardpanel.java


package org.nsomatrix;

import org.nsomatrix.SupabaseStorageClient;

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.io.File;
import java.util.List;

public class DashboardPanel extends JPanel {

    private String userEmail;
    private SupabaseStorageClient storageClient;

    private JLabel userLabel;
    private DefaultListModel<String> fileListModel = new DefaultListModel<>();
    private JList<String> fileList;

    private JButton uploadButton;
    private JButton downloadButton;
    private JButton deleteButton;
    private JButton refreshButton;

    public DashboardPanel() {
        setLayout(new BorderLayout(10, 10));
        setBorder(new EmptyBorder(10, 10, 10, 10));

        userLabel = new JLabel("Logged in as: ");
        add(userLabel, BorderLayout.NORTH);

        fileList = new JList<>(fileListModel);
        JScrollPane scrollPane = new JScrollPane(fileList);
        add(scrollPane, BorderLayout.CENTER);

        JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10));
        uploadButton = new JButton("Upload");
        downloadButton = new JButton("Download");
        deleteButton = new JButton("Delete");
        refreshButton = new JButton("Refresh");

        buttonsPanel.add(uploadButton);
        buttonsPanel.add(downloadButton);
        buttonsPanel.add(deleteButton);
        buttonsPanel.add(refreshButton);

        add(buttonsPanel, BorderLayout.SOUTH);

        uploadButton.addActionListener(e -> onUpload());
        refreshButton.addActionListener(e -> fetchFileList());
        downloadButton.addActionListener(e -> onDownload());
        deleteButton.addActionListener(e -> onDelete());
    }

    public void setUserEmail(String email) {
        this.userEmail = email;
        userLabel.setText("Logged in as: " + email);
    }

    public void setStorageClient(SupabaseStorageClient storageClient) {
        this.storageClient = storageClient;
        fetchFileList();
    }

    public void clearState() {
        userLabel.setText("Logged out.");
        fileListModel.clear();
        storageClient = null;
    }

    private void fetchFileList() {
        if (storageClient == null) return;

        fileListModel.clear();

        SwingWorker<List<String>, Void> worker = new SwingWorker<>() {
            @Override
            protected List<String> doInBackground() throws Exception {
                return storageClient.listFiles();
            }

            @Override
            protected void done() {
                try {
                    List<String> files = get();
                    fileListModel.clear();
                    if (files.isEmpty()) {
                        fileListModel.addElement("<no files>");
                    } else {
                        files.forEach(fileListModel::addElement);
                    }
                } catch (Exception e) {
                    // Print full stack trace to console for debugging
                    e.printStackTrace();

                    // Show detailed error message to user
                    String msg = e.getMessage();
                    if (e.getCause() != null) {
                        msg += "\nCause: " + e.getCause().getMessage();
                    }

                    JOptionPane.showMessageDialog(DashboardPanel.this,
                            "Failed to fetch file list:\n" + msg,
                            "Error",
                            JOptionPane.ERROR_MESSAGE);
                }
            }
        };
        worker.execute();
    }

    // onUpload, onDownload and onDelete methods unchanged, or similarly add try/catch with e.printStackTrace()
    private void onUpload() {
        if (storageClient == null) return;

        JFileChooser chooser = new JFileChooser();
        int result = chooser.showOpenDialog(this);
        if (result == JFileChooser.APPROVE_OPTION) {
            File selectedFile = chooser.getSelectedFile();

            SwingWorker<Void, Void> worker = new SwingWorker<>() {
                @Override
                protected Void doInBackground() throws Exception {
                    storageClient.uploadFile(selectedFile);
                    return null;
                }

                @Override
                protected void done() {
                    try {
                        get();
                        fetchFileList();
                        JOptionPane.showMessageDialog(DashboardPanel.this,
                                "Upload completed.", "Success", JOptionPane.INFORMATION_MESSAGE);
                    } catch (Exception e) {
                        e.printStackTrace();
                        JOptionPane.showMessageDialog(DashboardPanel.this,
                                "Upload failed:\n" + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
                    }
                }
            };
            worker.execute();
        }
    }

    private void onDownload() {
        if (storageClient == null) return;

        String selected = fileList.getSelectedValue();
        if (selected == null || selected.equals("<no files>")) {
            JOptionPane.showMessageDialog(this,
                    "Please select a file first.", "Warning", JOptionPane.WARNING_MESSAGE);
            return;
        }

        JFileChooser chooser = new JFileChooser();
        chooser.setSelectedFile(new File(selected));
        int result = chooser.showSaveDialog(this);
        if (result == JFileChooser.APPROVE_OPTION) {
            File saveFile = chooser.getSelectedFile();

            SwingWorker<Void, Void> worker = new SwingWorker<>() {
                @Override
                protected Void doInBackground() throws Exception {
                    storageClient.downloadFile(selected, saveFile);
                    return null;
                }

                @Override
                protected void done() {
                    try {
                        get();
                        JOptionPane.showMessageDialog(DashboardPanel.this,
                                "Download completed.", "Success", JOptionPane.INFORMATION_MESSAGE);
                    } catch (Exception e) {
                        e.printStackTrace();
                        JOptionPane.showMessageDialog(DashboardPanel.this,
                                "Download failed:\n" + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
                    }
                }
            };
            worker.execute();
        }
    }

    private void onDelete() {
        if (storageClient == null) return;

        String selected = fileList.getSelectedValue();
        if (selected == null || selected.equals("<no files>")) {
            JOptionPane.showMessageDialog(this,
                    "Please select a file first.", "Warning", JOptionPane.WARNING_MESSAGE);
            return;
        }

        int confirm = JOptionPane.showConfirmDialog(this,
                "Are you sure you want to delete '" + selected + "'?",
                "Confirm Delete",
                JOptionPane.YES_NO_OPTION);

        if (confirm == JOptionPane.YES_OPTION) {
            SwingWorker<Void, Void> worker = new SwingWorker<>() {
                @Override
                protected Void doInBackground() throws Exception {
                    storageClient.deleteFile(selected);
                    return null;
                }

                @Override
                protected void done() {
                    try {
                        get();
                        fetchFileList();
                        JOptionPane.showMessageDialog(DashboardPanel.this,
                                "Deleted successfully.", "Success", JOptionPane.INFORMATION_MESSAGE);
                    } catch (Exception e) {
                        e.printStackTrace();
                        JOptionPane.showMessageDialog(DashboardPanel.this,
                                "Delete failed:\n" + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
                    }
                }
            };
            worker.execute();
        }
    }
}

supabaseauthservice.java

package org.nsomatrix;

import java.io.IOException;
import java.net.URI;
import java.net.http.*;
import java.net.http.HttpResponse.BodyHandlers;
import java.net.http.HttpRequest.BodyPublishers;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Optional;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

public class SupabaseAuthService {

    private final HttpClient httpClient;
    private final ObjectMapper objectMapper;

    public SupabaseAuthService() {
        this.httpClient = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(15))
                .build();
        this.objectMapper = new ObjectMapper();
    }

    public Optional<AuthResponse> signUp(String email, String password) throws IOException, InterruptedException {
        return authRequest("/auth/v1/signup", email, password);
    }

    public Optional<AuthResponse> signIn(String email, String password) throws IOException, InterruptedException {
        return authRequest("/auth/v1/token?grant_type=password", email, password);
    }

    private Optional<AuthResponse> authRequest(String endpoint, String email, String password) throws IOException, InterruptedException {
        ObjectNode body = objectMapper.createObjectNode();
        body.put("email", email);
        body.put("password", password);

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(SupabaseConfig.SUPABASE_URL + endpoint))
                .header("Content-Type", "application/json")
                .header("apikey", SupabaseConfig.SUPABASE_ANON_KEY)
                .header("Authorization", "Bearer " + SupabaseConfig.SUPABASE_ANON_KEY)
                .POST(BodyPublishers.ofString(body.toString(), StandardCharsets.UTF_8))
                .build();

        HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString());

        if (response.statusCode() == 200 || response.statusCode() == 201) {
            AuthResponse authResponse = objectMapper.readValue(response.body(), AuthResponse.class);
            return Optional.of(authResponse);
        } else {
            System.err.println("Failed auth request, status: " + response.statusCode() + ", body: " + response.body());
            return Optional.empty();
        }
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class AuthResponse {
        public String access_token;
        public String refresh_token;
        public User user;

        @JsonIgnoreProperties(ignoreUnknown = true)
        public static class User {
            public String id;
            public String email;
            public String confirmed_at;  // Changed from Boolean to String to match timestamp format

            // add other fields as needed
        }

        @Override
        public String toString() {
            return "AuthResponse{" +
                    "access_token='" + access_token + '\'' +
                    ", refresh_token='" + refresh_token + '\'' +
                    ", user=" + user +
                    '}';
        }
    }
}

supabaseconfig.java

package org.nsomatrix;

public class SupabaseConfig {
    // Replace these with your own values:
    public static final String SUPABASE_URL = "https://hidden.supabase.co";
    public static final String SUPABASE_ANON_KEY = "hidden";
    public static final String STORAGE_BUCKET = "user-uploads";  // or your bucket name
}

supabaseloginpanel.java

package org.nsomatrix;

import org.nsomatrix.SupabaseAuthService;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.Optional;

public class SupabaseLoginPanel extends JPanel {

    private final JTextField emailField = new JTextField(20);
    private final JPasswordField passwordField = new JPasswordField(20);
    private final JButton loginButton = new JButton("Login");
    private final JButton signupButton = new JButton("Sign Up");

    private final JLabel statusLabel = new JLabel(" ");

    private final SupabaseAuthService authService;

    private LoginCallback loginCallback;

    public SupabaseLoginPanel() {
        this.authService = new SupabaseAuthService();
        initUI();
    }

    private void initUI() {
        setLayout(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();

        gbc.insets = new Insets(10, 10, 10, 10);

        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.anchor = GridBagConstraints.LINE_END;
        add(new JLabel("Email:"), gbc);

        gbc.gridx = 1;
        gbc.anchor = GridBagConstraints.LINE_START;
        add(emailField, gbc);

        gbc.gridx = 0;
        gbc.gridy = 1;
        gbc.anchor = GridBagConstraints.LINE_END;
        add(new JLabel("Password:"), gbc);

        gbc.gridx = 1;
        gbc.anchor = GridBagConstraints.LINE_START;
        add(passwordField, gbc);

        JPanel buttonPanel = new JPanel();
        buttonPanel.add(loginButton);
        buttonPanel.add(signupButton);

        gbc.gridx = 0;
        gbc.gridy = 2;
        gbc.gridwidth = 2;
        gbc.anchor = GridBagConstraints.CENTER;
        add(buttonPanel, gbc);

        gbc.gridy = 3;
        add(statusLabel, gbc);

        loginButton.addActionListener(this::onLogin);
        signupButton.addActionListener(this::onSignUp);
    }

    private void onLogin(ActionEvent e) {
        setStatus("Logging in...");
        loginButton.setEnabled(false);
        signupButton.setEnabled(false);

        SwingWorker<Void, Void> worker = new SwingWorker<>() {
            Optional<SupabaseAuthService.AuthResponse> resp;

            @Override
            protected Void doInBackground() {
                try {
                    resp = authService.signIn(emailField.getText().trim(), String.valueOf(passwordField.getPassword()));
                } catch (Exception ex) {
                    resp = Optional.empty();
                    ex.printStackTrace();
                }
                return null;
            }

            @Override
            protected void done() {
                loginButton.setEnabled(true);
                signupButton.setEnabled(true);
                if (resp.isPresent()) {
                    setStatus("Login successful!");
                    if (loginCallback != null) {
                        loginCallback.onLoginSuccess(resp.get());
                    }
                } else {
                    setStatus("Login failed. Check credentials and try again.");
                }
            }
        };

        worker.execute();
    }

    private void onSignUp(ActionEvent e) {
        setStatus("Signing up...");
        loginButton.setEnabled(false);
        signupButton.setEnabled(false);

        SwingWorker<Void, Void> worker = new SwingWorker<>() {
            Optional<SupabaseAuthService.AuthResponse> resp;

            @Override
            protected Void doInBackground() {
                try {
                    resp = authService.signUp(emailField.getText().trim(), String.valueOf(passwordField.getPassword()));
                } catch (Exception ex) {
                    resp = Optional.empty();
                    ex.printStackTrace();
                }
                return null;
            }

            @Override
            protected void done() {
                loginButton.setEnabled(true);
                signupButton.setEnabled(true);
                if (resp.isPresent()) {
                    setStatus("Sign up successful! You may now log in.");
                } else {
                    setStatus("Sign up failed. Try again.");
                }
            }
        };

        worker.execute();
    }

    public void setLoginCallback(LoginCallback callback) {
        this.loginCallback = callback;
    }

    private void setStatus(String message) {
        statusLabel.setText(message);
    }

    public interface LoginCallback {
        void onLoginSuccess(SupabaseAuthService.AuthResponse authResponse);
    }
}

supabasestorageclient.java

package org.nsomatrix;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.http.*;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.file.Files;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * SupabaseStorageClient - handles file operations in a Supabase storage bucket,
 * isolating files per user by prefixing paths with user UUID,
 * using user JWT for authenticated requests.
 */
public class SupabaseStorageClient {

    private final HttpClient httpClient;
    private final ObjectMapper objectMapper;

    private String accessToken;  // User JWT token
    private String userId;       // User UUID (used as folder prefix)

    /**
     * Constructor initializes HttpClient and ObjectMapper.
     */
    public SupabaseStorageClient() {
        this.httpClient = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(15))
                .build();
        this.objectMapper = new ObjectMapper();
    }

    /**
     * Sets the user JWT token for authenticated requests.
     * @param token JWT access token obtained upon user login
     */
    public void setAccessToken(String token) {
        this.accessToken = token;
    }

    /**
     * Sets the current user's UUID used for prefixing file paths.
     * @param userId UUID string representing the current user
     */
    public void setUserId(String userId) {
        this.userId = userId;
    }

    /**
     * Prepends the userId folder prefix to a given file path.
     * @param path File path relative to the user folder (can be empty)
     * @return Fully prefixed file path in storage bucket
     */
    private String prefixPath(String path) {
        if (userId == null || userId.isBlank()) {
            throw new IllegalStateException("User ID must be set before calling storage methods");
        }
        if (path == null || path.isBlank()) {
            return userId + "/";
        }
        return userId + "/" + path;
    }

    /**
     * Lists files in the logged-in user's folder.
     * @return List of file names (without userId prefix)
     * @throws IOException on network issues or bad response
     * @throws InterruptedException if request is interrupted
     */
    public List<String> listFiles() throws IOException, InterruptedException {
        String prefix = prefixPath("");

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(SupabaseConfig.SUPABASE_URL + "/storage/v1/object/list/" + SupabaseConfig.STORAGE_BUCKET + "?prefix=" + prefix))
                .GET()
                .header("apikey", SupabaseConfig.SUPABASE_ANON_KEY)
                .header("Authorization", "Bearer " + accessToken)
                .build();

        // Debug output
        System.out.println("[SupabaseStorageClient] List files request URL: " + request.uri());
        System.out.println("[SupabaseStorageClient] Bucket: " + SupabaseConfig.STORAGE_BUCKET);
        System.out.println("[SupabaseStorageClient] Access token present: " + (accessToken != null && !accessToken.isBlank()));

        HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString());

        if (response.statusCode() != 200) {
            throw new IOException("Failed to list files, status: " + response.statusCode() + ", body: " + response.body());
        }

        JsonNode root = objectMapper.readTree(response.body());
        List<String> files = new ArrayList<>();
        if (root.isArray()) {
            for (JsonNode item : root) {
                String fullName = item.get("name").asText();
                files.add(fullName.substring(prefix.length())); // Remove userId prefix
            }
        }
        return files;
    }

    /**
     * Uploads a file to the user's folder in the storage bucket.
     * @param file Local file to upload
     * @throws IOException on network or file IO errors
     * @throws InterruptedException if request is interrupted
     */
    public void uploadFile(File file) throws IOException, InterruptedException {
        String path = prefixPath(file.getName());

        HttpRequest.BodyPublisher bodyPublisher = HttpRequest.BodyPublishers.ofFile(file.toPath());

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(SupabaseConfig.SUPABASE_URL + "/storage/v1/object/" + SupabaseConfig.STORAGE_BUCKET + "/" + path))
                .header("apikey", SupabaseConfig.SUPABASE_ANON_KEY)
                .header("Authorization", "Bearer " + accessToken)
                .header("Content-Type", Files.probeContentType(file.toPath()))
                .PUT(bodyPublisher)
                .build();

        System.out.println("[SupabaseStorageClient] Upload file request URL: " + request.uri());
        System.out.println("[SupabaseStorageClient] Uploading file: " + file.getAbsolutePath());

        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() != 200) {
            throw new IOException("Failed to upload file, status: " + response.statusCode() + ", body: " + response.body());
        }
    }

    /**
     * Downloads a file from the user's folder in the storage bucket.
     * @param remoteFileName Filename in storage (without userId prefix)
     * @param localFile Local file to save the data
     * @throws IOException on network or file IO errors
     * @throws InterruptedException if request is interrupted
     */
    public void downloadFile(String remoteFileName, File localFile) throws IOException, InterruptedException {
        String path = prefixPath(remoteFileName);

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(SupabaseConfig.SUPABASE_URL + "/storage/v1/object/" + SupabaseConfig.STORAGE_BUCKET + "/" + path))
                .GET()
                .header("apikey", SupabaseConfig.SUPABASE_ANON_KEY)
                .header("Authorization", "Bearer " + accessToken)
                .build();

        System.out.println("[SupabaseStorageClient] Download file request URL: " + request.uri());
        System.out.println("[SupabaseStorageClient] Downloading to: " + localFile.getAbsolutePath());

        HttpResponse<InputStream> response = httpClient.send(request, BodyHandlers.ofInputStream());

        if (response.statusCode() != 200) {
            String body = new String(response.body().readAllBytes());
            throw new IOException("Failed to download file, status: " + response.statusCode() + ", body: " + body);
        }

        try (InputStream is = response.body();
             OutputStream os = Files.newOutputStream(localFile.toPath())) {
            is.transferTo(os);
        }
    }

    /**
     * Deletes a file from the user's folder in the storage bucket.
     * @param remoteFileName Filename in storage (without userId prefix)
     * @throws IOException on network errors
     * @throws InterruptedException if request is interrupted
     */
    public void deleteFile(String remoteFileName) throws IOException, InterruptedException {
        String path = prefixPath(remoteFileName);

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(SupabaseConfig.SUPABASE_URL + "/storage/v1/object/" + SupabaseConfig.STORAGE_BUCKET + "/" + path))
                .header("apikey", SupabaseConfig.SUPABASE_ANON_KEY)
                .header("Authorization", "Bearer " + accessToken)
                .DELETE()
                .build();

        System.out.println("[SupabaseStorageClient] Delete file request URL: " + request.uri());
        System.out.println("[SupabaseStorageClient] Deleting file: " + remoteFileName);

        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() != 204) {
            throw new IOException("Failed to delete file, status: " + response.statusCode() + ", body: " + response.body());
        }
    }
}