Compare commits

...

25 Commits

Author SHA1 Message Date
Tania Gutierrez dec1fd29f5
Merge branch 'develop' into riobuenoDevelops/refine-integration 2024-03-19 14:17:59 -04:00
Derrick Hammer 8770e24639
fix: wrap page in Authenticated component 2024-03-19 11:10:02 -04:00
Derrick Hammer 847f7537a3
fix: don't validate as email 2024-03-19 10:27:55 -04:00
Derrick Hammer 02cd490700
fix: update password needs the correct data sent to the mutation 2024-03-19 10:25:44 -04:00
Derrick Hammer 1dae0ba771
refactor: have UpdatePasswordFormRequest extend UpdatePasswordFormTypes 2024-03-19 10:23:03 -04:00
Derrick Hammer 03b1d761f0
fix: use ChangePasswordSchema 2024-03-19 10:16:22 -04:00
Derrick Hammer b65bd12c8a
feat: implement updatePassword 2024-03-19 10:12:13 -04:00
Derrick Hammer 8ca46c6f18
dep: update portal-sdk 2024-03-19 10:07:38 -04:00
Derrick Hammer 2dccaca132
fix: missing await 2024-03-19 09:12:21 -04:00
Derrick Hammer 3502732e76
fix: pass password to updateEmail 2024-03-19 08:59:10 -04:00
Derrick Hammer ddf9a0ddea
feat: initial update email support 2024-03-19 08:54:10 -04:00
Derrick Hammer 9fed7a2643
dep: update portal-sdk 2024-03-19 08:53:49 -04:00
Derrick Hammer ff5e5dec14
refactor: major refactor of the providers and resources to be more dynamic and flexible 2024-03-19 07:20:57 -04:00
Derrick Hammer 2e918e7802
refactor: we don't need to pass the endpoint, the sdk handles it 2024-03-19 07:18:52 -04:00
Derrick Hammer 482fd966cc
chore: remove unused property 2024-03-19 07:17:15 -04:00
Derrick Hammer 1ff5f205b2
feat: add callback and emit to track hashing progress 2024-03-18 18:51:41 -04:00
Derrick Hammer 0a51f3e76d
dep: update portal-sdk 2024-03-18 18:42:53 -04:00
Derrick Hammer c12c980b17
dep: update portal-sdk 2024-03-18 18:32:12 -04:00
Derrick Hammer 48f29a1338
refactor: set parallelUploads to 10 2024-03-18 18:20:24 -04:00
Derrick Hammer 9a9357bfc0
dep: update portal-sdk 2024-03-18 18:06:49 -04:00
Derrick Hammer 7293cb0b5a
refactor: switch to pulling the cookie from the account service after ping as ping now returns it in the response 2024-03-18 17:25:49 -04:00
Derrick Hammer ba374a851c
dep: update portal-sdk 2024-03-18 17:23:38 -04:00
Derrick Hammer d1e059fd71
fix: need to merge tus metadata to file meta 2024-03-18 16:48:29 -04:00
Derrick Hammer 90f14b63e5
build: add patch-package to package.json 2024-03-18 16:36:17 -04:00
Derrick Hammer 04c38429fd
fix: disable refine telemetry 2024-03-18 15:57:17 -04:00
12 changed files with 267 additions and 163 deletions

View File

@ -159,10 +159,7 @@ const UploadFileForm = () => {
state,
removeFile,
cancelAll,
} = useUppy({
uploader: "tus",
endpoint: import.meta.env.VITE_PUBLIC_TUS_ENDPOINT,
});
} = useUppy();
console.log({ state, files: getFiles() });

View File

@ -8,7 +8,7 @@ import DropTarget, {type DropTargetOptions} from "./uppy-dropzone"
import {useSdk} from "~/components/lib/sdk-context.js";
import UppyFileUpload from "~/components/lib/uppy-file-upload.js";
import {PROTOCOL_S5, Sdk} from "@lumeweb/portal-sdk";
import {S5Client} from "@lumeweb/s5-js";
import {S5Client, HashProgressEvent} from "@lumeweb/s5-js";
const LISTENING_EVENTS = [
"upload",
@ -19,11 +19,7 @@ const LISTENING_EVENTS = [
"files-added"
] as const
export function useUppy({
endpoint
}: {
endpoint: string
}) {
export function useUppy() {
const sdk = useSdk()
const [uploadLimit, setUploadLimit] = useState<number>(0)
@ -98,8 +94,24 @@ export function useUppy({
const file = uppyInstance.current?.getFile(fileID) as UppyFile
// @ts-ignore
if (file.uploader === "tus") {
const hashProgressCb = (event: HashProgressEvent) => {
uppyInstance.current?.emit("preprocess-progress", file, {
uploadStarted: false,
bytesUploaded: 0,
preprocess: {
mode: "determinate",
message: "Hashing file...",
value: Math.round((event.total / event.total) * 100)
}
})
}
const options = await sdk.protocols!().get<S5Client>(PROTOCOL_S5).getSdk().getTusOptions(file.data as File, {}, {onHashProgress: hashProgressCb})
uppyInstance.current?.setFileState(fileID, {
tus: await sdk.protocols!().get<S5Client>(PROTOCOL_S5).getSdk().getTusOptions(file.data as File)
tus: options,
meta: {
...options.metadata,
...file.meta,
}
})
}
}
@ -145,7 +157,7 @@ export function useUppy({
})
if (useTus) {
uppy.use(Tus, { endpoint: endpoint, limit: 6 })
uppy.use(Tus, { limit: 6, parallelUploads: 10 })
uppy.addPreProcessor(tusPreprocessor)
}
@ -195,7 +207,7 @@ export function useUppy({
})
}
setState("idle")
}, [targetRef, endpoint, uploadLimit])
}, [targetRef, uploadLimit])
useEffect(() => {
return () => {

View File

@ -0,0 +1,53 @@
import type {DataProvider, UpdateParams, UpdateResponse, HttpError} from "@refinedev/core";
import {SdkProvider} from "~/data/sdk-provider.js";
type AccountParams = {
email?: string;
password?: string;
}
type AccountData = AccountParams;
export const accountProvider: SdkProvider = {
getList: () => {
throw Error("Not Implemented")
},
getOne: () => {
throw Error("Not Implemented")
},
// @ts-ignore
async update<TVariables extends AccountParams = AccountParams>(
params: UpdateParams<AccountParams>,
): Promise<UpdateResponse<AccountData>> {
if (params.variables.email && params.variables.password) {
const ret = await accountProvider.sdk?.account().updateEmail(params.variables.email, params.variables.password);
if (ret) {
if (ret instanceof Error) {
return Promise.reject(ret)
}
} else {
return Promise.reject();
}
return {
data:
{
email: params.variables.email,
},
};
}
// Return an empty response if params.variables is undefined
return {
data: {} as AccountParams,
};
},
create: () => {
throw Error("Not Implemented")
},
deleteOne: () => {
throw Error("Not Implemented")
},
getApiUrl: () => "",
}

View File

@ -1,4 +1,4 @@
import type {AuthProvider} from "@refinedev/core"
import type {AuthProvider, UpdatePasswordFormTypes} from "@refinedev/core"
import type {
AuthActionResponse,
@ -8,7 +8,6 @@ import type {
// @ts-ignore
} from "@refinedev/core/dist/interfaces/bindings/auth"
import {Sdk} from "@lumeweb/portal-sdk";
import Cookies from 'universal-cookie';
import type {AccountInfoResponse} from "@lumeweb/portal-sdk";
export type AuthFormRequest = {
@ -32,134 +31,116 @@ export type Identity = {
email: string;
}
export class PortalAuthProvider implements RequiredAuthProvider {
constructor(apiUrl: string) {
this._sdk = Sdk.create(apiUrl);
export interface UpdatePasswordFormRequest extends UpdatePasswordFormTypes{
currentPassword: string;
}
const methods: Array<keyof AuthProvider> = [
'login',
'logout',
'check',
'onError',
'register',
'forgotPassword',
'updatePassword',
'getPermissions',
'getIdentity',
];
export const createPortalAuthProvider = (sdk: Sdk): AuthProvider => {
const maybeSetupAuth = (): void => {
const jwt = sdk.account().jwtToken;
if (jwt) {
sdk.setAuthToken(jwt);
}
};
methods.forEach((method) => {
this[method] = this[method]?.bind(this) as any;
});
}
return {
async login(params: AuthFormRequest): Promise<AuthActionResponse> {
const ret = await sdk.account().login({
email: params.email,
password: params.password,
});
private _sdk: Sdk;
let redirectTo: string | undefined;
get sdk(): Sdk {
return this._sdk;
}
public static create(apiUrl: string): AuthProvider {
return new PortalAuthProvider(apiUrl);
}
async login(params: AuthFormRequest): Promise<AuthActionResponse> {
const ret = await this._sdk.account().login({
email: params.email,
password: params.password,
})
let redirectTo: string | undefined;
if (ret) {
redirectTo = params.redirectTo;
if (!redirectTo) {
redirectTo = ret ? "/dashboard" : "/login";
if (ret) {
redirectTo = params.redirectTo;
if (!redirectTo) {
redirectTo = ret ? "/dashboard" : "/login";
}
sdk.setAuthToken(sdk.account().jwtToken);
}
this._sdk.setAuthToken(this._sdk.account().jwtToken);
}
return {
success: ret,
redirectTo,
};
}
return {
success: ret,
redirectTo,
};
},
async logout(params: any): Promise<AuthActionResponse> {
let ret = await this._sdk.account().logout();
return {success: ret, redirectTo: "/login"};
}
async logout(params: any): Promise<AuthActionResponse> {
let ret = await sdk.account().logout();
return {success: ret, redirectTo: "/login"};
},
async check(params?: any): Promise<CheckResponse> {
this.maybeSetupAuth();
async check(params?: any): Promise<CheckResponse> {
const ret = await sdk.account().ping();
const ret = await this._sdk.account().ping();
if (ret) {
maybeSetupAuth();
}
return {authenticated: ret, redirectTo: ret ? undefined : "/login"};
}
return {authenticated: ret, redirectTo: ret ? undefined : "/login"};
},
async onError(error: any): Promise<OnErrorResponse> {
const cookies = new Cookies();
return {logout: true};
}
async onError(error: any): Promise<OnErrorResponse> {
return {logout: true};
},
async register(params: RegisterFormRequest): Promise<AuthActionResponse> {
const ret = await this._sdk.account().register({
email: params.email,
password: params.password,
first_name: params.firstName,
last_name: params.lastName,
});
return {success: ret, redirectTo: ret ? "/dashboard" : undefined};
}
async register(params: RegisterFormRequest): Promise<AuthActionResponse> {
const ret = await sdk.account().register({
email: params.email,
password: params.password,
first_name: params.firstName,
last_name: params.lastName,
});
return {success: ret, redirectTo: ret ? "/dashboard" : undefined};
},
async forgotPassword(params: any): Promise<AuthActionResponse> {
return {success: true};
}
async forgotPassword(params: any): Promise<AuthActionResponse> {
return {success: true};
},
async updatePassword(params: any): Promise<AuthActionResponse> {
return {success: true};
}
async updatePassword(params: UpdatePasswordFormRequest): Promise<AuthActionResponse> {
maybeSetupAuth();
const ret = await sdk.account().updatePassword(params.currentPassword, params.password as string);
async getPermissions(params?: Record<string, any>): Promise<AuthActionResponse> {
return {success: true};
}
if (ret) {
if (ret instanceof Error) {
return {
success: false,
error: ret
}
}
async getIdentity(params?: Identity): Promise<IdentityResponse> {
this.maybeSetupAuth();
const ret = await this._sdk.account().info();
return {
success: true
}
} else {
return {
success: false
}
}
},
if (!ret) {
return {identity: null};
}
async getPermissions(params?: Record<string, any>): Promise<AuthActionResponse> {
return {success: true};
},
const acct = ret as AccountInfoResponse;
async getIdentity(params?: Identity): Promise<IdentityResponse> {
maybeSetupAuth();
const ret = await sdk.account().info();
return {
id: acct.id,
firstName: acct.first_name,
lastName: acct.last_name,
email: acct.email,
};
}
if (!ret) {
return {identity: null};
}
maybeSetupAuth(): void {
const cookies = new Cookies();
const jwtCookie = cookies.get('auth_token');
if (jwtCookie) {
this._sdk.setAuthToken(jwtCookie);
}
}
}
const acct = ret as AccountInfoResponse;
export interface RequiredAuthProvider extends AuthProvider {
login: AuthProvider['login'];
logout: AuthProvider['logout'];
check: AuthProvider['check'];
onError: AuthProvider['onError'];
register: AuthProvider['register'];
forgotPassword: AuthProvider['forgotPassword'];
updatePassword: AuthProvider['updatePassword'];
getPermissions: AuthProvider['getPermissions'];
getIdentity: AuthProvider['getIdentity'];
}
return {
id: acct.id,
firstName: acct.first_name,
lastName: acct.last_name,
email: acct.email,
};
},
};
};

View File

@ -1,10 +1,11 @@
import type { DataProvider } from "@refinedev/core";
import {SdkProvider} from "~/data/sdk-provider.js";
export const defaultProvider: DataProvider = {
export const fileProvider: SdkProvider = {
getList: () => { throw Error("Not Implemented") },
getOne: () => { throw Error("Not Implemented") },
update: () => { throw Error("Not Implemented") },
create: () => { throw Error("Not Implemented") },
deleteOne: () => { throw Error("Not Implemented") },
getApiUrl: () => "",
}
}

31
app/data/providers.ts Normal file
View File

@ -0,0 +1,31 @@
import type {AuthProvider, DataProvider} from "@refinedev/core";
import {fileProvider} from "~/data/file-provider.js";
import {Sdk} from "@lumeweb/portal-sdk";
import {accountProvider} from "~/data/account-provider.js";
import type {SdkProvider} from "~/data/sdk-provider.js";
import {createPortalAuthProvider} from "~/data/auth-provider.js";
interface DataProviders {
default: SdkProvider;
auth: AuthProvider;
[key: string]: SdkProvider | AuthProvider;
}
let providers: DataProviders;
export function getProviders(sdk: Sdk) {
if (providers) {
return providers;
}
accountProvider.sdk = sdk;
fileProvider.sdk = sdk;
providers = {
default: accountProvider,
auth: createPortalAuthProvider(sdk),
files: fileProvider,
};
return providers;
}

18
app/data/resources.ts Normal file
View File

@ -0,0 +1,18 @@
import type {ResourceProps} from "@refinedev/core";
const resources: ResourceProps[] = [
{
name: 'account',
meta: {
dataProviderName: 'default',
}
},
{
name: 'file',
meta: {
dataProviderName: 'files',
}
}
];
export default resources;

6
app/data/sdk-provider.ts Normal file
View File

@ -0,0 +1,6 @@
import {DataProvider} from "@refinedev/core";
import {Sdk} from "@lumeweb/portal-sdk";
export interface SdkProvider extends DataProvider {
sdk?: Sdk;
}

View File

@ -1,24 +1,18 @@
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
import {Links, Meta, Outlet, Scripts, ScrollRestoration,} from "@remix-run/react";
import stylesheet from "./tailwind.css?url";
import type { LinksFunction } from "@remix-run/node";
import type {LinksFunction} from "@remix-run/node";
// Supports weights 200-800
import '@fontsource-variable/manrope';
import {Refine} from "@refinedev/core";
import routerProvider from "@refinedev/remix-router";
import { defaultProvider } from "~/data/file-provider";
import {PortalAuthProvider} from "~/data/auth-provider";
import { notificationProvider } from "~/data/notification-provider";
import {SdkContextProvider} from "~/components/lib/sdk-context";
import { Toaster } from "~/components/ui/toaster";
import {getProviders} from "~/data/providers.js";
import {Sdk} from "@lumeweb/portal-sdk";
import resources from "~/data/resources.js";
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet },
@ -44,19 +38,18 @@ export function Layout({children}: { children: React.ReactNode }) {
}
export default function App() {
const auth = PortalAuthProvider.create("https://alpha.pinner.xyz")
const sdk = Sdk.create(import.meta.env.VITE_PORTAL_URL)
const providers = getProviders(sdk);
return (
<Refine
authProvider={auth}
authProvider={providers.auth}
routerProvider={routerProvider}
dataProvider={defaultProvider}
notificationProvider={notificationProvider}
resources={[
{ name: 'files' },
{ name: 'users' }
]}
dataProvider={providers.default}
resources={resources}
options={{disableTelemetry: true}}
>
<SdkContextProvider sdk={(auth as PortalAuthProvider).sdk}>
<SdkContextProvider sdk={sdk}>
<Outlet/>
</SdkContextProvider>
</Refine>

View File

@ -3,10 +3,11 @@ import { getZodConstraint, parseWithZod } from "@conform-to/zod";
import { DialogClose } from "@radix-ui/react-dialog";
import { Cross2Icon } from "@radix-ui/react-icons";
import {
BaseKey,
useGetIdentity,
useUpdate,
useUpdatePassword,
Authenticated,
BaseKey,
useGetIdentity,
useUpdate,
useUpdatePassword,
} from "@refinedev/core";
import { useMemo, useState } from "react";
import { z } from "zod";
@ -40,6 +41,7 @@ import { Input } from "~/components/ui/input";
import { UsageCard } from "~/components/usage-card";
import QRImg from "~/images/QR.png";
import {UpdatePasswordFormRequest} from "~/data/auth-provider.js";
export default function MyAccount() {
const { data: identity } = useGetIdentity<{ email: string }>();
@ -52,7 +54,8 @@ export default function MyAccount() {
});
return (
<GeneralLayout>
<Authenticated key="account" v3LegacyAuthProviderCompatible>
<GeneralLayout>
<h1 className="text-lg font-bold mb-4">My Account</h1>
<Dialog
onOpenChange={(open) => {
@ -211,6 +214,7 @@ export default function MyAccount() {
</DialogContent>
</Dialog>
</GeneralLayout>
</Authenticated>
);
}
@ -247,10 +251,11 @@ const ChangeEmailForm = ({ currentValue }: { currentValue: string }) => {
const data = Object.fromEntries(new FormData(e.currentTarget).entries());
console.log(identity);
updateEmail({
resource: "users",
id: identity?.id || "",
resource: "account",
id: "",
values: {
email: data.email.toString(),
password: data.password.toString(),
},
});
},
@ -292,7 +297,7 @@ const ChangeEmailForm = ({ currentValue }: { currentValue: string }) => {
const ChangePasswordSchema = z
.object({
currentPassword: z.string().email(),
currentPassword: z.string(),
newPassword: z.string(),
retypePassword: z.string(),
})
@ -311,7 +316,7 @@ const ChangePasswordForm = () => {
const { mutate: updatePassword } = useUpdatePassword<{ password: string }>();
const [form, fields] = useForm({
id: "login",
constraint: getZodConstraint(ChangeEmailSchema),
constraint: getZodConstraint(ChangePasswordSchema),
onValidate({ formData }) {
return parseWithZod(formData, { schema: ChangePasswordSchema });
},
@ -322,7 +327,7 @@ const ChangePasswordForm = () => {
const data = Object.fromEntries(new FormData(e.currentTarget).entries());
updatePassword({
password: data.newPassword.toString(),
password: data.newPassword.toString(),
});
},
});
@ -408,10 +413,7 @@ const ChangeAvatarForm = () => {
state,
removeFile,
cancelAll,
} = useUppy({
uploader: "tus",
endpoint: import.meta.env.VITE_PUBLIC_TUS_ENDPOINT,
});
} = useUppy();
console.log({ state, files: getFiles() });

View File

@ -8,13 +8,15 @@
"dev": "remix vite:dev",
"lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
"preview": "vite preview",
"typecheck": "tsc"
"typecheck": "tsc",
"patch-package": "patch-package",
"postinstall": "patch-package"
},
"dependencies": {
"@conform-to/react": "^1.0.2",
"@conform-to/zod": "^1.0.2",
"@fontsource-variable/manrope": "^5.0.19",
"@lumeweb/portal-sdk": "0.0.0-20240318183202",
"@lumeweb/portal-sdk": "0.0.0-20240319140708",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
@ -44,7 +46,6 @@
"react-dom": "^18.2.0",
"tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7",
"universal-cookie": "^7.1.0",
"zod": "^3.22.4"
},
"devDependencies": {

9
vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_PORTAL_API_URL: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}