diff --git a/packages/components/src/api/beta_key/ListBetaKeys.ts b/packages/components/src/api/beta_key/ListBetaKeys.ts new file mode 100644 index 0000000000..45ea29bce9 --- /dev/null +++ b/packages/components/src/api/beta_key/ListBetaKeys.ts @@ -0,0 +1,58 @@ +import MakeRequest from "../MakeRequest"; +import { Pagination } from "../_common/SharedFetchTypes"; + +export interface BetaKey { + created_at: Date; + creator: { + default_avatar: { + color_index: number; + image_index: number; + }; + display_name: string; + gravatar_hash: string; + user_token: string; + username: string; + }; + key_value: string; + maybe_redeemed_at?: Date; + maybe_redeemer?: { + default_avatar: { + color_index: number; + image_index: number; + }; + display_name: string; + gravatar_hash: string; + user_token: string; + username: string; + } | null; + maybe_referrer?: { + default_avatar: { + color_index: number; + image_index: number; + }; + display_name: string; + gravatar_hash: string; + user_token: string; + username: string; + } | null; + product: string; + token: string; +} + +export interface ListBetaKeysRequest {} + +export interface ListBetaKeysResponse { + beta_keys: BetaKey[]; + pagination: Pagination; + success: boolean; +} + +export const ListBetaKeys = MakeRequest< + string, + ListBetaKeysRequest, + ListBetaKeysResponse, + {} +>({ + method: "GET", + routingFunction: () => "/v1/beta_keys/list", +}); diff --git a/packages/components/src/api/media_files/ListMediaFiles.ts b/packages/components/src/api/media_files/ListMediaFiles.ts index c13f626c4e..21432857c9 100644 --- a/packages/components/src/api/media_files/ListMediaFiles.ts +++ b/packages/components/src/api/media_files/ListMediaFiles.ts @@ -1,11 +1,11 @@ import MakeRequest from "../MakeRequest"; import { MediaFile } from "./GetMedia"; -import { LazyPagination } from "../_common/SharedFetchTypes"; +import { Pagination } from "../_common/SharedFetchTypes"; export interface ListMediaFilesRequest {} export interface ListMediaFilesResponse { - pagination: LazyPagination; + pagination: Pagination; success: boolean; results: MediaFile[]; } diff --git a/packages/fakeyou.com/package.json b/packages/fakeyou.com/package.json index 51c3f24fdf..cc93be4009 100644 --- a/packages/fakeyou.com/package.json +++ b/packages/fakeyou.com/package.json @@ -18,6 +18,7 @@ "@react-spring/types": "^9.7.3", "@react-spring/web": "^9.7.3", "@storyteller/components": "0.1.0", + "@tanstack/react-table": "^8.17.3", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", diff --git a/packages/fakeyou.com/src/components/common/TempSelect/TempSelect.tsx b/packages/fakeyou.com/src/components/common/TempSelect/TempSelect.tsx index e5c9e98a3e..85202af87a 100644 --- a/packages/fakeyou.com/src/components/common/TempSelect/TempSelect.tsx +++ b/packages/fakeyou.com/src/components/common/TempSelect/TempSelect.tsx @@ -23,6 +23,7 @@ export interface SelectProps extends ReactSelectProps { options: Option[]; onChange?: (value: any) => void; required?: boolean; + className?: string; } export default function Select({ @@ -40,13 +41,13 @@ export default function Select({ const isMulti = Array.isArray(value); const findVal = (opts: Option[], nest = 0): Option | undefined => { let val: Option | undefined; - opts.forEach((option) => { + opts.forEach(option => { if (!val) { if (option.options) { - val = findVal(option.options,++nest); + val = findVal(option.options, ++nest); } else if (option.value === value) { - val = option - } + val = option; + } } }); return val; @@ -55,11 +56,17 @@ export default function Select({ const valueLabel = findVal(options)?.label || ""; const onChange = (option: any, x: any) => { if (Array.isArray(option)) { - inChange({ target: {value: option.map(({ value = "" }) => value), name, type: "select" } }); + inChange({ + target: { + value: option.map(({ value = "" }) => value), + name, + type: "select", + }, + }); } else { - inChange({ target: {value: option.value, name, type: "select" } }); + inChange({ target: { value: option.value, name, type: "select" } }); } - } + }; const className = `${icon ? " input-icon" : ""}${ small ? " select-small" : "" }`; @@ -97,9 +104,14 @@ export default function Select({ name, onChange, options, - ...value && (isMulti ? { - value: Array.isArray(value) ? value.map((val: any) => ({ label: val, value: val })) : [] - } : { value: { label: valueLabel, value } }), + ...(value && + (isMulti + ? { + value: Array.isArray(value) + ? value.map((val: any) => ({ label: val, value: val })) + : [], + } + : { value: { label: valueLabel, value } })), ...rest, }} /> diff --git a/packages/fakeyou.com/src/components/providers/SessionProvider/SessionProvider.tsx b/packages/fakeyou.com/src/components/providers/SessionProvider/SessionProvider.tsx index b253c97c35..082d649d2a 100644 --- a/packages/fakeyou.com/src/components/providers/SessionProvider/SessionProvider.tsx +++ b/packages/fakeyou.com/src/components/providers/SessionProvider/SessionProvider.tsx @@ -14,7 +14,8 @@ export enum ModalView { // ignore this modal stuff for now -V Login, } -interface ModalProps { // this too +interface ModalProps { + // this too close: () => void; open: () => void; view: ModalView; @@ -32,7 +33,7 @@ interface SessionContextType { querySubscriptions?: any; sessionFetched: boolean; sessionSubscriptions?: any; - studioAccessCheck: (x:any) => any, + studioAccessCheck: (x: any) => any; user?: any; userTokenMatch: (token: string) => boolean; } @@ -75,12 +76,18 @@ export default function SessionProvider({ sessionSubscriptions, sessionWrapper, }: Props) { - const sessionResponse = sessionWrapper?.sessionStateResponse || { logged_in: false, user: null }; + const sessionResponse = sessionWrapper?.sessionStateResponse || { + logged_in: false, + user: null, + }; const { logged_in: loggedIn, user } = sessionResponse; const [view, viewSet] = useState(ModalView.Closed); const open = () => viewSet(ModalView.Signup); - const close = () => { viewSet(ModalView.Closed); }; - const viewSwitch = () => viewSet(view === ModalView.Signup ? ModalView.Login : ModalView.Signup); + const close = () => { + viewSet(ModalView.Closed); + }; + const viewSwitch = () => + viewSet(view === ModalView.Signup ? ModalView.Login : ModalView.Signup); const check = () => { if (user) { return true; @@ -90,7 +97,9 @@ export default function SessionProvider({ } }; const userTokenMatch = (otherUserToken: string) => - !otherUserToken || !user?.user_token ? false : user.user_token === otherUserToken; + !otherUserToken || !user?.user_token + ? false + : user.user_token === otherUserToken; const canEditTtsModel = (userToken: string) => user?.can_delete_other_users_tts_models || userTokenMatch(userToken); const canEditMediaFile = (userToken: string) => @@ -98,11 +107,14 @@ export default function SessionProvider({ const canBanUsers = () => user?.can_ban_users || false; const canAccessStudio = () => { const hostnameAllowed = StudioRolloutHostnameAllowed(); - const userAllowed = !!user?.can_access_studio || !!user?.maybe_feature_flags.includes("studio"); + const userAllowed = + !!user?.can_access_studio || + !!user?.maybe_feature_flags.includes("studio"); return hostnameAllowed && userAllowed; - } + }; - const studioAccessCheck = (content: React.ElementType) => canAccessStudio() ? content : ; + const studioAccessCheck = (content: React.ElementType) => + canAccessStudio() ? content : ; const modal = { close, open, view }; diff --git a/packages/fakeyou.com/src/v2/view/PageContainer.tsx b/packages/fakeyou.com/src/v2/view/PageContainer.tsx index e9429581d3..92d9cd859d 100644 --- a/packages/fakeyou.com/src/v2/view/PageContainer.tsx +++ b/packages/fakeyou.com/src/v2/view/PageContainer.tsx @@ -138,6 +138,7 @@ import StyleVideo from "./pages/style-video"; import CreateBetaKeyPage from "./pages/beta_key/CreateBetaKeyPage"; import RedeemBetaKeyPage from "./pages/beta_key/RedeemBetaKeyPage"; import RedeemSuccessPage from "./pages/beta_key/RedeemSuccessPage"; +import BetaKeysListPage from "./pages/beta_key/BetaKeysListPage"; interface Props { sessionWrapper: SessionWrapper; @@ -1139,6 +1140,10 @@ class PageContainer extends React.Component< + + + + {/* // // }) => { + const columnFilterValue = column.getFilterValue(); + + if (column.id === "maybe_redeemed_at") { + return ( + // column.setFilterValue(e.target.value)} + // className="my-2 py-1" + // options={[ + // { value: "", label: "All" }, + // { value: "redeemed", label: "Redeemed" }, + // { value: "not_redeemed", label: "Not Redeemed" }, + // ]} + // />\ + null + ); + } + + return ( + column.setFilterValue(e.target.value)} + placeholder={`Search...`} + className="my-2 py-1" + /> + ); +}; + +export default function BetaKeysListPage() { + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + + const [columnFilters, setColumnFilters] = useState([]); + const [sorting, setSorting] = useState([]); + + useEffect(() => { + const fetchData = async () => { + try { + const response: ListBetaKeysResponse = await ListBetaKeys("", {}); + if (response.success) { + setData(response.beta_keys); + } else { + console.error("Error fetching data: API call unsuccessful"); + } + } catch (error) { + console.error("Error fetching data:", error); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, []); + + const columnHelper = createColumnHelper(); + + const columns = [ + columnHelper.accessor("maybe_redeemed_at", { + header: "Redeemed?", + cell: info => { + const value = info.getValue(); + return value ? ( + + ) : ( + + ); + }, + meta: { + className: "status-cell", + headerClassName: "status-header", + canFilter: true, + } as CustomColumnMeta, + filterFn: (row, columnId, filterValue) => { + const redeemed = row.getValue(columnId); + if (filterValue === "redeemed") { + return !!redeemed; + } + if (filterValue === "not_redeemed") { + return !redeemed; + } + return true; + }, + }), + columnHelper.accessor("key_value", { + header: "Key", + cell: info => { + const key = info.getValue(); + + if (key) { + return key; + } else { + return "********"; + } + }, + }), + columnHelper.accessor("maybe_referrer.username", { + header: "Referrer", + cell: info => { + const username = info.getValue(); + const userData = info.row.original.maybe_referrer; + const userEmailHash = userData?.gravatar_hash || ""; + + return username ? ( +
+ + {username} +
+ ) : ( + "None" + ); + }, + }), + columnHelper.accessor("maybe_redeemer.username", { + header: "Redeemed by", + cell: info => { + const username = info.getValue(); + const userData = info.row.original.maybe_redeemer; + const userEmailHash = userData?.gravatar_hash || ""; + return username ? ( +
+ + {username} +
+ ) : ( + "-" + ); + }, + }), + columnHelper.accessor("created_at", { + header: "Created Date", + cell: info => new Date(info.getValue()).toLocaleDateString(), + }), + columnHelper.accessor("maybe_redeemed_at", { + header: "Redemption Date", + cell: info => { + const value = info.getValue(); + return value ? new Date(value).toLocaleDateString() : "-"; + }, + }), + columnHelper.accessor("creator.username", { + header: "Key Creator", + cell: info => { + const username = info.getValue(); + const userData = info.row.original.creator; + const userEmailHash = userData?.gravatar_hash || ""; + return ( +
+ + {username} +
+ ); + }, + }), + ]; + + const table = useReactTable({ + data, + columns, + state: { + columnFilters, + sorting, + }, + onColumnFiltersChange: setColumnFilters, + onSortingChange: setSorting, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getSortedRowModel: getSortedRowModel(), + }); + + return ( + + + +
+ {loading ? ( +
Loading...
+ ) : ( + <> +
+ + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + + ))} + + ))} + + + {table.getRowModel().rows.map(row => ( + + {row.getVisibleCells().map(cell => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + <> +
+ {flexRender( + header.column.columnDef.header, + header.getContext() + )} + {header.column.getIsSorted() ? ( + header.column.getIsSorted() === "asc" ? ( + + ) : ( + + ) + ) : null} +
+ {header.column.getCanFilter() ? ( +
+ +
+ ) : null} + + )} +
+ {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} +
+
+ {data.length === 0 && ( +
No data available
+ )} + + )} +
+
+
+ ); +} diff --git a/packages/fakeyou.com/src/v2/view/pages/beta_key/CreateBetaKeyPage.tsx b/packages/fakeyou.com/src/v2/view/pages/beta_key/CreateBetaKeyPage.tsx index a156d74675..8f557603bb 100644 --- a/packages/fakeyou.com/src/v2/view/pages/beta_key/CreateBetaKeyPage.tsx +++ b/packages/fakeyou.com/src/v2/view/pages/beta_key/CreateBetaKeyPage.tsx @@ -101,11 +101,10 @@ export default function CreateBetaKeyPage() {
setUsername(e.target.value)} - required={true} /> { history.push("/beta-key/redeem/success"); @@ -78,6 +78,8 @@ export default function RedeemBetaKeyPage() { ); } + console.log(user); + return ( <> diff --git a/yarn.lock b/yarn.lock index d508ce673c..8d3fc3575e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4472,6 +4472,7 @@ __metadata: resolution: "@storyteller/root@workspace:." dependencies: "@react-spring/types": ^9.7.3 + "@tanstack/react-table": ^8.17.3 "@typeform/embed": ^4.8.1 "@typeform/embed-react": ^3.15.0 craco: ^0.0.3 @@ -4757,6 +4758,25 @@ __metadata: languageName: node linkType: hard +"@tanstack/react-table@npm:^8.17.3": + version: 8.17.3 + resolution: "@tanstack/react-table@npm:8.17.3" + dependencies: + "@tanstack/table-core": 8.17.3 + peerDependencies: + react: ">=16.8" + react-dom: ">=16.8" + checksum: ec635f9c6e66bc5655e71ef5bb5087c78fca79e619d8cf80ba395152d88e72ef78e87cbb3e4b9e0b4d86504068c8bf5ba320309529ef9d3dd1ee4630bf75067a + languageName: node + linkType: hard + +"@tanstack/table-core@npm:8.17.3": + version: 8.17.3 + resolution: "@tanstack/table-core@npm:8.17.3" + checksum: 4f7115d6198f63bea6a00a1c8f33288453463afbede716365b99b3ffa565c46861eb2903c26d3d1de35211ad85fa13bc1e0f35b3546b8f7fc46218441cf06700 + languageName: node + linkType: hard + "@testing-library/dom@npm:*": version: 8.11.3 resolution: "@testing-library/dom@npm:8.11.3"