Tigris example with Next.js (#42662)

This commit is contained in:
Adil Ansari 2022-11-15 03:00:29 -08:00 committed by GitHub
parent efbabd2a5e
commit 0f8b3d6c8b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1296 additions and 0 deletions

View file

@ -0,0 +1,4 @@
# DO NOT ADD SECRETS TO THIS FILE. This is a good place for defaults.
# If you want to add secrets use `.env.development.local` instead.
TIGRIS_URI=localhost:8081

View file

@ -0,0 +1,7 @@
# Enter your tigris uri, ex :- localhost:8081, api.preview.tigrisdata.cloud etc.
TIGRIS_URI=
# Client credentials, if using auth, can be generated from Tigris cloud console.
# See: https://docs.tigrisdata.com/auth
TIGRIS_CLIENT_ID=
TIGRIS_CLIENT_SECRET=

View file

@ -0,0 +1,4 @@
# DO NOT ADD SECRETS TO THIS FILE. This is a good place for defaults.
# If you want to add secrets use `.env.production.local` instead.
TIGRIS_URI=api.preview.tigrisdata.cloud

27
examples/with-tigris/.gitignore vendored Normal file
View file

@ -0,0 +1,27 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View file

@ -0,0 +1,171 @@
# ⚡ Tigris example app on Next.js - Todo list
A simple todo app built on [Next.js][next-url] and [Tigris](https://docs.tigrisdata.com/)
using [TypeScript client](https://docs.tigrisdata.com/typescript/), deployed on [Vercel][vercel-url].
### Project demo
https://tigris-nextjs-starter-kit.vercel.app/
# ⚙️ Deploying your own
All you need is a [Github](https://github.com), [Vercel][vercel-url] and Tigris
account([sign up for a free account](https://www.tigrisdata.com/nextjs#signup-form)). Now, Hit "Deploy"
and follow instructions to deploy app to your Vercel account
[![Deploy with Vercel](https://vercel.com/button)][deploy-url]
:tada: All done. You should be able to use app on the URL provided by Vercel. Feel free to play around
or do a [code walkthrough](#code-walkthrough) next :tada:
> [Tigris integration](https://vercel.com/integrations/tigris) with Vercel will automatically fetch
> access keys to populate [Environment Variables](.env.local.example) when deploying app.
<details>
<summary>2. Running Next.js server & Tigris dev environment on your local computer</summary>
## 📖 Running Next.js server & Tigris locally
### Prerequisites
1. Tigris installed on your dev computer
1. For **macOS**: `brew install tigrisdata/tigris/tigris-cli`
2. Other operating systems: [See installation instructions here](https://docs.tigrisdata.com/cli/installation)
2. Node.js version 16+
### Instructions
1. Clone this repo on your computer
```shell
git clone https://github.com/tigrisdata/tigris-vercel-starter
```
2. Install dependencies
```shell
cd tigris-vercel-starter
npm install
```
3. Start Tigris local development environment
```shell
tigris dev start
```
4. Run the Next.js server
```shell
npm run dev
```
> Note: This step uses a custom dev & build script to initialize Tigris database and collection for
> the app and requires [ts-node](https://www.npmjs.com/package/ts-node#installation) to be installed.
:tada: All done. You should be able to use app on `localhost:3000` in browser. Feel free to play
around or do a [code walk-through](#code-walkthrough) next :tada:
</details>
# 📖 How to use
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example:
```bash
npx create-next-app --example with-tigris tigris-next-app
```
```bash
yarn create next-app --example with-tigris tigris-next-app
```
```bash
pnpm create next-app --example with-tigris tigris-next-app
```
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
# 👀 Code walkthrough
<details>
<summary> 📂 File structure</summary>
```text
├── package.json
├── lib
│ ├── tigris.ts
├── models
│ └── tigris
│ └── todoStarterApp
│ └── todoItems.ts
└── pages
├── index.tsx
└── api
├── item
│ ├── [id].ts
└── items
├── index.ts
└── search.ts
```
</details>
<details>
<summary> 🪢 Tigris schema definition</summary>
[models/tigris/todoStarterApp/todoItems.ts](models/tigris/todoStarterApp/todoItems.ts) - The to-do list app
has a single collection `todoItems` that stores the to-do items in `todoStarterApp` database. The
Database and Collection get automatically provisioned by the [setup script](scripts/setup.ts).
This is an inspiration from Next.js based file system router. Create a folder or drop a schema file
inside database folder under `models/tigris/`, and you're able to instantly create Databases and
Collections in Tigris for your application.
</details>
<details>
<summary> 🌐 Connecting to Tigris</summary>
[lib/tigris.ts](lib/tigris.ts) - Loads the environment variables you specified previously in creating a Vercel project
section and uses them to configure the Tigris client.
</details>
<details>
<summary> ❇️ API routes to access data in Tigris collection</summary>
All the Next.js API routes are defined under `pages/api/`. We have three files exposing endpoints:
#### [`pages/api/items/index.ts`](pages/api/items/index.ts)
- `GET /api/items` to get an array of to-do items as Array<TodoItem>
- `POST /api/items` to add an item to the list
#### [`/pages/api/items/search.ts`](/pages/api/items/search.ts)
- `GET /api/items/search?q=query` to find and return items matching the given query
#### [`pages/api/item/[id].ts`](pages/api/item/[id].ts)
- `GET /api/item/{id}` to fetch an item
- `PUT /api/item/{id}` to update the given item
- `DELETE /api/item/[id]` to delete an item
</details>
# 🚀 Next steps
In a few steps, we learnt how to bootstrap a Next.js app using Tigris and deploy it on Vercel. Feel
free to add more functionalities or customize App for your use-case and learn more about
[Tigris data platform](https://docs.tigrisdata.com/overview/)
<!-- MARKDOWN LINKS & IMAGES -->
[typescript]: https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white
[typescript-url]: https://www.typescriptlang.org/
[vercel]: https://img.shields.io/badge/vercel-F22F46?style=for-the-badge&logo=vercel&logoColor=white
[vercel-url]: https://vercel.com/
[deploy-url]: https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Ftigrisdata%2Ftigris-vercel-starter&project-name=todo-list-app-tigris&repo-name=todo-list-webapp-tigris&demo-title=My%20To-do%20list%20webapp&demo-description=A%20To-do%20list%20webapp%20using%20NextJS%20and%20Tigris&integration-ids=oac_Orjx197uMuJobdSaEpVv2Zn8
[next.js]: https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white
[next-url]: https://nextjs.org/

View file

@ -0,0 +1,47 @@
import Image from 'next/image'
import React from 'react'
import { TodoItem } from '../models/tigris/todoStarterApp/todoItems'
import styles from '../styles/EachToDo.module.css'
type Props = {
toDoItem: TodoItem
deleteHandler: (id?: number) => void
updateHandler: (item: TodoItem) => void
}
const EachTodo = ({ toDoItem, deleteHandler, updateHandler }: Props) => {
return (
<>
<li className={styles.each}>
<button
className={styles.eachButton}
onClick={() => {
updateHandler(toDoItem)
}}
>
<Image
src={toDoItem.completed ? '/circle-checked.svg' : '/circle.svg'}
layout="fixed"
width={20}
height={20}
alt="Check Image"
/>
<span
style={toDoItem.completed ? { textDecoration: 'line-through' } : {}}
>
{toDoItem.text}
</span>
</button>
<button
className={styles.deleteBtn}
onClick={() => {
deleteHandler(toDoItem.id)
}}
>
<Image src="/delete.svg" width={24} height={24} alt="Check Image" />
</button>
</li>
</>
)
}
export default EachTodo

View file

@ -0,0 +1,14 @@
import React from 'react'
import styles from '../styles/LoaderWave.module.css'
const LoaderWave = () => {
return (
<div className={styles.holder}>
<div className={`${styles.loader} ${styles.l1}`}></div>
<div className={`${styles.loader} ${styles.l2}`}></div>
<div className={`${styles.loader} ${styles.l3}`}></div>
</div>
)
}
export default LoaderWave

View file

@ -0,0 +1,28 @@
import { DB, Tigris } from '@tigrisdata/core'
const DB_NAME = 'todoStarterApp'
declare global {
// eslint-disable-next-line no-var
var tigrisDb: DB
}
let tigrisDb: DB
// Caching the client because `next dev` would otherwise create a
// new connection on every file save while previous connection is active due to
// hot reloading. However, in production, Next.js would completely tear down before
// restarting, thus, disconnecting and reconnecting to Tigris.
if (process.env.NODE_ENV !== 'production') {
if (!global.tigrisDb) {
const tigrisClient = new Tigris()
global.tigrisDb = tigrisClient.getDatabase(DB_NAME)
}
tigrisDb = global.tigrisDb
} else {
const tigrisClient = new Tigris()
tigrisDb = tigrisClient.getDatabase(DB_NAME)
}
// export to share DB across modules
export default tigrisDb

View file

@ -0,0 +1,22 @@
import {
TigrisCollectionType,
TigrisDataTypes,
TigrisSchema,
} from '@tigrisdata/core/dist/types'
export const COLLECTION_NAME = 'todoItems'
export interface TodoItem extends TigrisCollectionType {
id?: number
text: string
completed: boolean
}
export const TodoItemSchema: TigrisSchema<TodoItem> = {
id: {
type: TigrisDataTypes.INT32,
primary_key: { order: 1, autoGenerate: true },
},
text: { type: TigrisDataTypes.STRING },
completed: { type: TigrisDataTypes.BOOLEAN },
}

View file

@ -0,0 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
swcMinify: true,
}
module.exports = nextConfig

View file

@ -0,0 +1,23 @@
{
"private": true,
"scripts": {
"predev": "APP_ENV=development npm run setup",
"dev": "next dev",
"build": "next build",
"postbuild": "APP_ENV=production npm run setup",
"start": "next start",
"setup": "npx ts-node scripts/setup.ts"
},
"dependencies": {
"@tigrisdata/core": "beta",
"next": "latest",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@types/node": "18.11.2",
"@types/react": "18.0.21",
"@types/react-dom": "18.0.6",
"typescript": "4.8.4"
}
}

View file

@ -0,0 +1,9 @@
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import React from 'react'
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
export default MyApp

View file

@ -0,0 +1,85 @@
import { NextApiRequest, NextApiResponse } from 'next'
import {
COLLECTION_NAME,
TodoItem,
} from '../../../models/tigris/todoStarterApp/todoItems'
import tigrisDb from '../../../lib/tigris'
type Data = {
result?: TodoItem
error?: string
}
async function handleGet(
req: NextApiRequest,
res: NextApiResponse<Data>,
itemId: number
) {
try {
const itemsCollection = tigrisDb.getCollection<TodoItem>(COLLECTION_NAME)
const item = await itemsCollection.findOne({ id: itemId })
if (!item) {
res.status(404).json({ error: 'No item found' })
} else {
res.status(200).json({ result: item })
}
} catch (err) {
const error = err as Error
res.status(500).json({ error: error.message })
}
}
async function handlePut(req: NextApiRequest, res: NextApiResponse<Data>) {
try {
const item = JSON.parse(req.body) as TodoItem
const itemsCollection = tigrisDb.getCollection<TodoItem>(COLLECTION_NAME)
const updated = await itemsCollection.insertOrReplaceOne(item)
res.status(200).json({ result: updated })
} catch (err) {
const error = err as Error
res.status(500).json({ error: error.message })
}
}
async function handleDelete(
req: NextApiRequest,
res: NextApiResponse<Data>,
itemId: number
) {
try {
const itemsCollection = tigrisDb.getCollection<TodoItem>(COLLECTION_NAME)
const status = (await itemsCollection.deleteOne({ id: itemId })).status
if (status === 'deleted') {
res.status(200).json({})
} else {
res.status(500).json({ error: `Failed to delete ${itemId}` })
}
} catch (err) {
const error = err as Error
res.status(500).json({ error: error.message })
}
}
// GET /api/item/[id] -- gets item from collection where id = [id]
// PUT /api/item/[id] {ToDoItem} -- updates the item in collection where id = [id]
// DELETE /api/item/[id] -- deletes the item in collection where id = [id]
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const { id } = req.query
switch (req.method) {
case 'GET':
await handleGet(req, res, Number(id))
break
case 'PUT':
await handlePut(req, res)
break
case 'DELETE':
await handleDelete(req, res, Number(id))
break
default:
res.setHeader('Allow', ['GET', 'PUT', 'DELETE'])
res.status(405).end(`Method ${req.method} Not Allowed`)
}
}

View file

@ -0,0 +1,54 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import {
COLLECTION_NAME,
TodoItem,
} from '../../../models/tigris/todoStarterApp/todoItems'
import tigrisDb from '../../../lib/tigris'
type Response = {
result?: Array<TodoItem>
error?: string
}
async function handleGet(req: NextApiRequest, res: NextApiResponse<Response>) {
try {
const itemsCollection = tigrisDb.getCollection<TodoItem>(COLLECTION_NAME)
const cursor = itemsCollection.findMany()
const items = await cursor.toArray()
res.status(200).json({ result: items })
} catch (err) {
const error = err as Error
res.status(500).json({ error: error.message })
}
}
async function handlePost(req: NextApiRequest, res: NextApiResponse<Response>) {
try {
const item = JSON.parse(req.body) as TodoItem
const itemsCollection = tigrisDb.getCollection<TodoItem>(COLLECTION_NAME)
const inserted = await itemsCollection.insertOne(item)
res.status(200).json({ result: [inserted] })
} catch (err) {
const error = err as Error
res.status(500).json({ error: error.message })
}
}
// GET /api/items -- gets items from collection
// POST /api/items {ToDoItem} -- inserts a new item to collection
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Response>
) {
switch (req.method) {
case 'GET':
await handleGet(req, res)
break
case 'POST':
await handlePost(req, res)
break
default:
res.setHeader('Allow', ['GET', 'POST'])
res.status(405).end(`Method ${req.method} Not Allowed`)
}
}

View file

@ -0,0 +1,37 @@
import { NextApiRequest, NextApiResponse } from 'next'
import {
COLLECTION_NAME,
TodoItem,
} from '../../../models/tigris/todoStarterApp/todoItems'
import { SearchRequest } from '@tigrisdata/core/dist/search/types'
import tigrisDb from '../../../lib/tigris'
type Data = {
result?: Array<TodoItem>
error?: string
}
// GET /api/items/search?q=searchQ -- searches for items matching text `searchQ`
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const query = req.query['q']
if (query === undefined) {
res.status(400).json({ error: 'No search query found in request' })
return
}
try {
const itemsCollection = tigrisDb.getCollection<TodoItem>(COLLECTION_NAME)
const searchRequest: SearchRequest<TodoItem> = { q: query as string }
const searchResult = await itemsCollection.search(searchRequest)
const items = new Array<TodoItem>()
for (const hit of searchResult.hits) {
items.push(hit.document)
}
res.status(200).json({ result: items })
} catch (err) {
const error = err as Error
res.status(500).json({ error: error.message })
}
}

View file

@ -0,0 +1,255 @@
import type { NextPage } from 'next'
import Image from 'next/image'
import Head from 'next/head'
import React, { useEffect, useState } from 'react'
import EachTodo from '../components/EachToDo'
import LoaderWave from '../components/LoaderWave'
import { TodoItem } from '../models/tigris/todoStarterApp/todoItems'
import styles from '../styles/Home.module.css'
const Home: NextPage = () => {
// This is the input field
const [textInput, setTextInput] = useState('')
// Todo list array which displays the todo items
const [todoList, setTodoList] = useState<TodoItem[]>([])
// Loading and Error flags for the template
const [isLoading, setIsLoading] = useState(false)
const [isError, setIsError] = useState(false)
// This is use to animate the input text field
const [wiggleError, setWiggleError] = useState(false)
// Two separate views. 1. List view for todo items & 2. Search result view
type viewModeType = 'list' | 'search'
const [viewMode, setViewMode] = useState<viewModeType>('list')
// Util search query/input check
/*
The is a helper util method, that validtes the input field via a regex and returns a true or false.
This also wiggles the text input if the regex doesn't find any match.
*/
const queryCheckWiggle = () => {
const result: RegExpMatchArray | null = textInput.match('^\\S.{0,100}$')
if (result === null) {
setWiggleError(true)
return true
}
return false
}
useEffect(() => {
if (!wiggleError) {
return
}
const timeOut = setTimeout(() => {
setWiggleError(false)
}, 500)
return () => clearTimeout(timeOut)
}, [wiggleError])
// Search query
/*
'searchQuery' method takes the state from 'textInput' and send it over to the 'api/items/search' endpoint via a query param 'q'.
The response is the same as the response from "fetch('/api/items')", an array of TodoItems if successful.
*/
const searchQuery = () => {
if (queryCheckWiggle()) {
return
}
setIsLoading(true)
fetch(`/api/items/search?q=${encodeURI(textInput)}`, {
method: 'GET',
})
.then((response) => response.json())
.then((data) => {
setIsLoading(false)
if (data.result) {
setViewMode('search')
setTodoList(data.result)
}
})
}
// Fetch Todo List
/*
'fetchListItems' is the first method that's called when the component is mounted from the useEffect below.
This sets some of the state like 'isLoading' and 'isError' before it fetches for data from the endpoint defined under 'pages/api/items/index'.
The api endpoint returns a json with the key 'result' and a status 200 if successful or returns a status 500 along with the 'error' key.
If the 'result' key is present we safely set the 'todoList'.
*/
const fetchListItems = () => {
setIsLoading(true)
setIsError(false)
fetch('/api/items')
.then((response) => response.json())
.then((data) => {
setIsLoading(false)
if (data.result) {
setViewMode('list')
setTodoList(data.result)
} else {
setIsError(true)
}
})
.catch(() => {
setIsLoading(false)
setIsError(true)
})
}
// Load the initial list of todo-items
useEffect(() => {
fetchListItems()
}, [])
// Add a new todo-item
/*
'addToDoItem' takes the 'textInput' state, creates a 'TodoItem' & converts it to a JSON.
Sends it over as body payload to the api endpoint; which is how the api expects and is defined in 'pages/api/items' 'POST' switch.
*/
const addToDoItem = () => {
if (queryCheckWiggle()) {
return
}
setIsLoading(true)
fetch('/api/items', {
method: 'POST',
body: JSON.stringify({ text: textInput, completed: false }),
}).then(() => {
setIsLoading(false)
setTextInput('')
fetchListItems()
})
}
// Delete Todo-item
/*
'deleteTodoItem' requires an id value of the TodoItem. When the user presses the 'delete'(cross) button from a TodoItem, this method is invoked.
It calls the endpoint 'api/item/<id>' with the 'DELETE' method. Read the method 'handleDelete' under pages/api/item/[id]' to learn more how the api handles deletion.
*/
const deleteTodoItem = (id?: number) => {
setIsLoading(true)
fetch('/api/item/' + id, {
method: 'DELETE',
}).then(() => {
setIsLoading(false)
if (viewMode === 'list') {
fetchListItems()
} else {
searchQuery()
}
})
}
// Update Todo-item (mark complete/incomplete)
/*
'updateTodoItem' takes the TodoItem object, inverts the 'completed' boolean and calls the same endpoint as 'deletion' but with a different method 'PUT'.
Navigate to 'api/item/[id]' and read more how the api handles updates under the 'handlePut' method.
*/
const updateTodoItem = (item: TodoItem) => {
item.completed = !item.completed
setIsLoading(true)
fetch('/api/item/' + item.id, {
method: 'PUT',
body: JSON.stringify(item),
}).then(() => {
setIsLoading(false)
if (viewMode === 'list') {
fetchListItems()
} else {
searchQuery()
}
})
}
return (
<div>
<Head>
<title>Todo App using Next.js + Tigris</title>
<meta name="description" content="Tigris app tutorial" />
</Head>
<div className={styles.container}>
<h2>Sample Todo app using Next.js and Tigris</h2>
{/* Search Header */}
<div className={styles.searchHeader}>
<input
className={`${styles.searchInput} ${
wiggleError ? styles.invalid : ''
}`}
value={textInput}
onChange={(e) => {
setWiggleError(false)
setTextInput(e.target.value)
}}
placeholder="Type an item to add or search"
/>
<button onClick={addToDoItem}>Add</button>
<button onClick={searchQuery}>Search</button>
</div>
{/* Results section */}
<div className={styles.results}>
{/* Loader, Errors and Back to List mode */}
{isError && (
<p className={styles.errorText}>Something went wrong.. </p>
)}
{isLoading && <LoaderWave />}
{viewMode === 'search' && (
<button
className={styles.clearSearch}
onClick={() => {
setTextInput('')
fetchListItems()
}}
>
Go back to list
</button>
)}
{/* Todo Item List */}
{todoList.length < 1 ? (
<p className={styles.noItems}>
{viewMode === 'search'
? 'No items found.. '
: 'Add a todo by typing in the field above and hit Add!'}
</p>
) : (
<ul>
{todoList.map((each) => {
return (
<EachTodo
key={each.id}
toDoItem={each}
deleteHandler={deleteTodoItem}
updateHandler={updateTodoItem}
/>
)
})}
</ul>
)}
</div>
<a href="https://tigrisdata.com/">
<Image
src="/tigris_logo.svg"
alt="Tigris Logo"
width={100}
height={100}
/>
</a>
</div>
</div>
)
}
export default Home

View file

@ -0,0 +1,4 @@
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.2721 3.125C6.47477 3.125 3.39709 6.20268 3.39709 10C3.39709 13.7973 6.47477 16.875 10.2721 16.875C14.0694 16.875 17.1471 13.7973 17.1471 10C17.1471 6.20268 14.0694 3.125 10.2721 3.125ZM2.14709 10C2.14709 5.51232 5.78442 1.875 10.2721 1.875C14.7598 1.875 18.3971 5.51232 18.3971 10C18.3971 14.4877 14.7598 18.125 10.2721 18.125C5.78442 18.125 2.14709 14.4877 2.14709 10Z" fill="#878787"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.0474 7.89139C14.2914 8.13546 14.2914 8.53119 14.0474 8.77527L9.88069 12.9419C9.63662 13.186 9.24089 13.186 8.99681 12.9419L6.49681 10.4419C6.25273 10.1979 6.25273 9.80213 6.49681 9.55805C6.74089 9.31398 7.13662 9.31398 7.38069 9.55805L9.43875 11.6161L13.1635 7.89139C13.4076 7.64731 13.8033 7.64731 14.0474 7.89139Z" fill="#878787"/>
</svg>

After

Width:  |  Height:  |  Size: 931 B

View file

@ -0,0 +1,3 @@
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.2721 3.125C6.47477 3.125 3.39709 6.20268 3.39709 10C3.39709 13.7973 6.47477 16.875 10.2721 16.875C14.0694 16.875 17.1471 13.7973 17.1471 10C17.1471 6.20268 14.0694 3.125 10.2721 3.125ZM2.14709 10C2.14709 5.51232 5.78442 1.875 10.2721 1.875C14.7598 1.875 18.3971 5.51232 18.3971 10C18.3971 14.4877 14.7598 18.125 10.2721 18.125C5.78442 18.125 2.14709 14.4877 2.14709 10Z" fill="#878787"/>
</svg>

After

Width:  |  Height:  |  Size: 544 B

View file

@ -0,0 +1,5 @@
<svg width="23" height="24" viewBox="0 0 23 24" fill="#ff0000" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.7517 8.30088C15.0307 8.58229 15.0307 9.03855 14.7517 9.31995L9.359 14.758C9.07994 15.0394 8.6275 15.0394 8.34844 14.758C8.06938 14.4766 8.06938 14.0204 8.34844 13.7389L13.7411 8.30088C14.0202 8.01948 14.4726 8.01948 14.7517 8.30088Z" fill="#878787"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.34844 8.30088C8.6275 8.01948 9.07994 8.01948 9.359 8.30088L14.7517 13.7389C15.0307 14.0204 15.0307 14.4766 14.7517 14.758C14.4726 15.0394 14.0202 15.0394 13.7411 14.758L8.34844 9.31995C8.06938 9.03855 8.06938 8.58229 8.34844 8.30088Z" fill="#878787"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5501 3.60296C7.20849 3.60296 3.68971 7.15135 3.68971 11.5295C3.68971 15.9076 7.20849 19.456 11.5501 19.456C15.8916 19.456 19.4104 15.9076 19.4104 11.5295C19.4104 7.15135 15.8916 3.60296 11.5501 3.60296ZM2.26055 11.5295C2.26055 6.35541 6.41919 2.16177 11.5501 2.16177C16.6809 2.16177 20.8396 6.35541 20.8396 11.5295C20.8396 16.7035 16.6809 20.8971 11.5501 20.8971C6.41919 20.8971 2.26055 16.7035 2.26055 11.5295Z" fill="#878787"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 944 657"><defs><style>.g{fill:url(#e);}.h{fill:url(#d);}.i{fill:url(#f);}.j{fill:url(#c);}.k{fill:url(#b);}.l{fill:#edb61e;}</style><linearGradient id="b" x1="-242.31" y1="1031.9" x2="130.67" y2="972.5" gradientTransform="translate(564 1316) scale(1 -1)" gradientUnits="userSpaceOnUse"><stop offset=".09" stop-color="#57c0a6"/><stop offset=".65" stop-color="#94d3c7"/></linearGradient><linearGradient id="c" x1="-326.21" y1="1078.65" x2="545.43" y2="711.97" xlink:href="#b"/><linearGradient id="d" x1="-87.97" y1="1048.93" x2="132.06" y2="1017.84" xlink:href="#b"/><linearGradient id="e" x1="-384.48" y1="1051.97" x2="-124.55" y2="1011.31" xlink:href="#b"/><linearGradient id="f" x1="39.02" y1="1050.09" x2="347.66" y2="991.98" xlink:href="#b"/></defs><path class="k" d="M451.78,234.54v-10.98h27.78v108.14c.14,9.48-1.93,18.87-6.06,27.42-3.95,8.03-10.24,14.68-18.05,19.09-8.91,4.84-18.96,7.23-29.11,6.92-13.75,.65-27.32-3.32-38.53-11.28-9.75-7.56-16.11-18.65-17.69-30.86h27.34c1.41,5.29,4.69,9.9,9.25,12.96,5.27,3.34,11.45,4.97,17.69,4.67,3.63,.16,7.25-.4,10.66-1.64,3.41-1.24,6.54-3.14,9.21-5.59,2.72-2.95,4.8-6.44,6.1-10.23,1.3-3.79,1.8-7.82,1.46-11.81v-21.47c-2.7,7.23-7.64,13.4-14.11,17.63-7.1,4.54-15.41,6.85-23.84,6.61-8.79,.12-17.45-2.2-24.99-6.7-7.59-4.68-13.63-11.47-17.39-19.53-4.14-9.46-6.28-19.66-6.28-29.98s2.14-20.52,6.28-29.98c3.75-8.05,9.8-14.83,17.39-19.49,7.54-4.52,16.2-6.84,24.99-6.7,9.25,.44,30.52,.44,37.91,12.79Zm-50.25,20.5c-5.16,6.68-7.96,14.87-7.96,23.3s2.8,16.62,7.96,23.3c5.64,5.43,13.19,8.45,21.04,8.41,7.84-.04,15.36-3.14,20.94-8.63,5.35-6.53,8.28-14.71,8.28-23.14s-2.92-16.61-8.28-23.14c-2.68-2.82-5.93-5.05-9.54-6.53-3.6-1.48-7.49-2.19-11.38-2.07-3.92-.17-7.82,.5-11.46,1.97-3.63,1.47-6.91,3.7-9.6,6.54h0Z"/><path class="j" d="M429.66,435.39c-6.65,0-13.28-.47-19.86-1.41-46.54-6.74-82.9-35.57-92.68-73.44-2.3-12.92-3.14-26.05-2.52-39.15v-51.98h27.87v52.2c-.48,10.7,.07,21.41,1.64,32,6.94,27.02,34.33,47.79,69.67,52.9,27.12,3.92,61.93-1.81,78.92-29.49l4.42-8.6c13.93-28.3,44.5-28.57,60.87-28.74h10.17c2.95-.43,5.82-1.32,8.49-2.64,4.97-2.42,9.16-6.19,12.1-10.86,2.94-4.67,4.51-10.07,4.53-15.59v-41h27.87v41c-.02,13.92-5.12,27.36-14.35,37.81-9.23,10.45-21.96,17.19-35.82,18.97h-12.74c-22.12,.22-31.36,3.53-36.14,13.22l-5.09,10.14c-17.21,28.26-49.59,44.66-87.37,44.66Z"/><path class="h" d="M530.16,237.32v-14.28h-28v104.26c0,.41,.1,.81,.29,1.17,.19,.36,.48,.66,.83,.88,.35,.22,.74,.34,1.15,.36,.41,.02,.82-.06,1.18-.25,7.21-3.36,14.8-5.86,22.61-7.45,.54-.11,1.03-.4,1.39-.83,.35-.42,.55-.96,.56-1.51v-39.68c0-19.01,10.32-28.52,30.97-28.52h8.14v-29.84c-8.58-.18-31.01,1.41-39.11,15.69Z"/><path class="l" d="M329.06,257.46l-2.88-6.3c-1.65-3.56-4.54-6.4-8.14-7.98l-6.37-2.69,6.28-2.87c3.59-1.63,6.45-4.54,8.01-8.16l2.74-6.35,2.88,6.26c1.62,3.59,4.54,6.44,8.18,7.98l6.33,2.73-6.28,2.87c-3.59,1.63-6.45,4.54-8.01,8.16l-2.74,6.35Z"/><path class="g" d="M261.65,275.14v-28.83h27.34v-23.54h-79.63v23.54h23.84v28.7c0,29.36,12.47,63.61,55.78,63.61v-22.79c-22.12,.13-27.34-16.88-27.34-40.69Z"/><path class="l" d="M607.22,257.46l-2.88-6.3c-1.65-3.57-4.56-6.41-8.18-7.98l-6.37-2.69,6.32-2.87c3.58-1.65,6.43-4.55,8.01-8.16l2.74-6.35,2.83,6.26c1.62,3.59,4.55,6.44,8.18,7.98l6.37,2.73-6.28,2.87c-3.59,1.63-6.45,4.54-8.01,8.16l-2.74,6.35Z"/><path class="i" d="M728.48,285.5c-4.11-4.32-9.25-7.52-14.95-9.3-7.24-2.16-14.63-3.78-22.12-4.85-6.83-.98-13.55-2.65-20.04-4.98-1.88-.59-3.53-1.76-4.69-3.35-1.16-1.59-1.78-3.51-1.77-5.47,.04-1.94,.63-3.83,1.69-5.46,1.06-1.63,2.56-2.93,4.32-3.75,5.51-2.62,11.6-3.82,17.69-3.48,11.39-.16,22.62,2.76,32.47,8.46l10.18-20.06c-5.95-3.36-12.4-5.76-19.11-7.1-7.65-1.75-15.47-2.63-23.31-2.64-12.85-.67-25.58,2.69-36.41,9.61-4.2,2.83-7.61,6.67-9.93,11.16-2.32,4.49-3.47,9.49-3.34,14.54-.54,6.9,1.61,13.74,6.02,19.09,4.14,4.5,9.41,7.82,15.26,9.61,7.47,2.2,15.1,3.82,22.83,4.85,6.6,.86,13.1,2.34,19.42,4.41,1.78,.52,3.33,1.61,4.42,3.1,1.09,1.49,1.65,3.3,1.6,5.14,0,8.35-7.92,12.51-23.76,12.48-14.39,.32-28.7-2.18-42.11-7.36v22.04c12.92,4.82,26.65,7.11,40.43,6.74,15.84,0,28.36-3.16,37.56-9.48,4.28-2.71,7.79-6.46,10.2-10.91,2.41-4.45,3.63-9.43,3.55-14.48,.46-6.74-1.73-13.4-6.1-18.56h0Z"/></svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View file

@ -0,0 +1,31 @@
<svg width="383" height="156" viewBox="0 0 383 156" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M176.73 9.54529V1.54207H196.983V80.3785C197.087 87.2934 195.574 94.1365 192.564 100.368C189.683 106.22 185.098 111.07 179.407 114.284C172.908 117.814 165.584 119.555 158.186 119.329C148.162 119.804 138.268 116.906 130.096 111.102C122.985 105.593 118.348 97.5063 117.196 88.6046H137.127C138.154 92.4622 140.549 95.82 143.867 98.0547C147.711 100.49 152.217 101.679 156.767 101.46C159.41 101.576 162.05 101.172 164.535 100.267C167.02 99.3628 169.302 97.9772 171.247 96.1904C173.233 94.0383 174.748 91.4978 175.697 88.7317C176.647 85.9656 177.009 83.0342 176.762 80.1212V64.4694C174.794 69.7371 171.19 74.241 166.474 77.3248C161.299 80.6378 155.24 82.3179 149.091 82.1455C142.68 82.2324 136.372 80.541 130.87 77.2604C125.339 73.8519 120.932 68.9013 118.196 63.0225C115.177 56.1294 113.619 48.69 113.619 41.169C113.619 33.648 115.177 26.2086 118.196 19.3155C120.933 13.4454 125.34 8.50454 130.87 5.10899C136.366 1.81416 142.679 0.121629 149.091 0.223887C155.831 0.545271 171.344 0.546527 176.73 9.54529ZM140.093 24.4894C136.33 29.3573 134.29 35.33 134.29 41.475C134.29 47.62 136.33 53.5911 140.093 58.4591C144.205 62.4207 149.71 64.6218 155.429 64.5918C161.149 64.5618 166.629 62.3036 170.698 58.2991C174.602 53.5362 176.734 47.5757 176.734 41.4264C176.734 35.277 174.602 29.3166 170.698 24.5537C168.742 22.4975 166.373 20.8757 163.744 19.7957C161.115 18.7157 158.286 18.2013 155.444 18.2861C152.589 18.1626 149.741 18.6533 147.092 19.7235C144.443 20.7937 142.056 22.419 140.093 24.4894Z" fill="url(#paint0_linear_1413_753)"/>
<path d="M160.604 155.969C155.759 155.971 150.92 155.626 146.124 154.939C112.197 150.022 85.6881 129.004 78.5608 101.398C76.8849 91.9823 76.2681 82.4089 76.7224 72.8574C76.7224 71.4755 76.7224 70.1901 76.7224 69.0331V34.9661H97.04V69.0331C97.04 70.2544 97.04 71.5728 97.04 73.019C96.6886 80.8167 97.0878 88.6292 98.2328 96.3508C103.296 116.052 123.259 131.189 149.026 134.917C168.796 137.777 194.177 133.599 206.561 113.417L209.786 107.149C219.944 86.516 242.229 86.3231 254.161 86.1946H261.579C263.732 85.8805 265.822 85.231 267.771 84.2675C271.395 82.4996 274.451 79.7565 276.594 76.349C278.737 72.9416 279.881 69.0056 279.897 64.9844V35.0947H300.214V64.9844C300.204 75.1336 296.486 84.931 289.756 92.5485C283.026 100.166 273.744 105.082 263.643 106.378H263.062H254.355C238.23 106.539 231.489 108.949 228.006 116.02L224.298 123.411C211.753 144.012 188.146 155.969 160.604 155.969Z" fill="url(#paint1_linear_1413_753)"/>
<path d="M233.876 11.5694V1.15577H213.462V77.1644C213.459 77.4615 213.532 77.7541 213.675 78.015C213.817 78.2758 214.024 78.4956 214.277 78.6537C214.529 78.8117 214.819 78.9024 215.116 78.9173C215.414 78.9322 215.711 78.8705 215.978 78.7384C221.237 76.2878 226.769 74.465 232.458 73.3072C232.853 73.2267 233.21 73.0143 233.468 72.7046C233.726 72.3949 233.87 72.007 233.876 71.6045V42.68C233.876 28.8176 241.401 21.8857 256.451 21.8857H262.386V0.127902C256.129 -0.000651479 239.778 1.15652 233.876 11.5694Z" fill="url(#paint2_linear_1413_753)"/>
<path d="M87.2685 26.2567L85.1718 21.6619C83.9707 19.0674 81.8612 16.9988 79.2383 15.8446L74.5945 13.8831L79.1737 11.7944C81.7936 10.6047 83.8749 8.48465 85.0111 5.84845L87.0102 1.2207L89.1069 5.78568C90.2903 8.4038 92.4203 10.4796 95.0727 11.6014L99.6842 13.5943L95.105 15.683C92.4851 16.8727 90.4038 18.9927 89.2676 21.6289L87.2685 26.2567Z" fill="#EFB700"/>
<path d="M38.1196 39.145V18.1263H37.7968H58.0498V0.964905H0V18.1263H17.3831V37.9555C17.3831 38.2126 17.3831 38.4053 17.3831 38.6303V39.0477C17.3831 60.4519 26.4772 85.424 58.0498 85.424V68.8087C41.9249 68.9051 38.1196 56.4998 38.1196 39.145Z" fill="url(#paint3_linear_1413_753)"/>
<path d="M290.056 26.2567L287.96 21.6619C286.756 19.0582 284.631 16.9873 281.993 15.8446L277.349 13.8831L281.961 11.7944C284.573 10.5946 286.652 8.47774 287.798 5.84845L289.798 1.2207L291.862 5.78568C293.045 8.4038 295.176 10.4796 297.828 11.6014L302.472 13.5943L297.893 15.683C295.273 16.8727 293.192 18.9927 292.055 21.6289L290.056 26.2567Z" fill="#EFB700"/>
<path d="M378.453 46.6978C375.459 43.5486 371.706 41.2132 367.552 39.9155C362.273 38.3397 356.883 37.1577 351.427 36.3799C346.447 35.6663 341.551 34.4499 336.818 32.7486C335.446 32.3216 334.247 31.4673 333.399 30.31C332.552 29.1527 332.1 27.7537 332.11 26.321C332.142 24.9058 332.57 23.5278 333.345 22.3413C334.12 21.1549 335.211 20.2074 336.496 19.6045C340.515 17.6947 344.95 16.8215 349.396 17.0655C357.702 16.9464 365.883 19.079 373.067 23.2358L380.484 8.61343C376.145 6.16326 371.442 4.41554 366.552 3.43801C360.977 2.1646 355.277 1.51905 349.557 1.51096C340.193 1.02227 330.909 3.47164 323.015 8.51614C319.955 10.5799 317.465 13.3785 315.774 16.6527C314.084 19.927 313.246 23.5719 313.34 27.2531C312.945 32.282 314.517 37.2687 317.726 41.1693C320.744 44.4485 324.585 46.8675 328.852 48.176C334.299 49.7822 339.863 50.9629 345.494 51.71C350.304 52.3368 355.043 53.4128 359.651 54.9239C360.947 55.3004 362.08 56.0959 362.874 57.1836C363.668 58.2712 364.077 59.5898 364.037 60.9341C364.037 67.019 358.264 70.051 346.719 70.0295C336.231 70.2661 325.798 68.4426 316.017 64.6627V80.7319C325.434 84.2448 335.442 85.9137 345.494 85.6484C357.039 85.6484 366.166 83.3454 372.874 78.7389C375.995 76.7658 378.555 74.0285 380.311 70.7875C382.067 67.5465 382.96 63.9108 382.903 60.228C383.24 55.313 381.644 50.4605 378.453 46.6978V46.6978Z" fill="url(#paint4_linear_1413_753)"/>
<defs>
<linearGradient id="paint0_linear_1413_753" x1="94.9076" y1="-36.0466" x2="366.823" y2="7.2553" gradientUnits="userSpaceOnUse">
<stop offset="0.0885417" stop-color="#4EC8AB"/>
<stop offset="0.645833" stop-color="#81F4D8"/>
</linearGradient>
<linearGradient id="paint1_linear_1413_753" x1="26.3855" y1="-1.86078" x2="661.838" y2="265.459" gradientUnits="userSpaceOnUse">
<stop offset="0.0885417" stop-color="#4EC8AB"/>
<stop offset="0.645833" stop-color="#81F4D8"/>
</linearGradient>
<linearGradient id="paint2_linear_1413_753" x1="202.482" y1="-23.8636" x2="362.894" y2="-1.19487" gradientUnits="userSpaceOnUse">
<stop offset="0.0885417" stop-color="#4EC8AB"/>
<stop offset="0.645833" stop-color="#81F4D8"/>
</linearGradient>
<linearGradient id="paint3_linear_1413_753" x1="-13.0285" y1="-24.74" x2="176.47" y2="4.90543" gradientUnits="userSpaceOnUse">
<stop offset="0.0885417" stop-color="#4EC8AB"/>
<stop offset="0.645833" stop-color="#81F4D8"/>
</linearGradient>
<linearGradient id="paint4_linear_1413_753" x1="297.644" y1="-24.1842" x2="522.657" y2="18.1791" gradientUnits="userSpaceOnUse">
<stop offset="0.0885417" stop-color="#4EC8AB"/>
<stop offset="0.645833" stop-color="#81F4D8"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -0,0 +1,4 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,19 @@
import { Tigris } from '@tigrisdata/core'
import { loadEnvConfig } from '@next/env'
// Run the config loader only when not executing within next runtime
if (process.env.NODE_ENV === undefined) {
if (process.env.APP_ENV === 'development') {
loadEnvConfig(process.cwd(), true)
} else if (process.env.APP_ENV === 'production') {
loadEnvConfig(process.cwd())
}
}
async function main() {
// setup client and register schemas
const tigrisClient = new Tigris()
await tigrisClient.registerSchemas('models/tigris')
}
main()

View file

@ -0,0 +1,58 @@
.each {
display: flex;
flex-direction: row;
border-bottom: 1px solid #dddddd;
padding: 10px 25px;
animation: fadeInAnimation 0.4s cubic-bezier(0.62, 0.57, 0, 1.02);
}
.each:last-child {
border-bottom: none;
}
.eachButton {
background-color: transparent !important;
width: 100%;
text-align: left;
vertical-align: middle;
padding: 0 !important;
}
.eachButton:hover img {
content: url('/circle-checked.svg');
}
.eachButton:hover span {
text-decoration: line-through;
}
.each span {
vertical-align: middle;
padding-left: 10px;
font-size: 14px;
}
.deleteBtn {
background-color: transparent !important;
text-align: center !important;
min-width: 20px !important;
padding: 10px !important;
}
.deleteBtn:hover,
.deleteBtn:focus,
.deleteBtn:active,
.deleteBtn:focus-visible {
filter: brightness(0) saturate(100%) invert(98%) sepia(27%) saturate(4410%)
hue-rotate(308deg) brightness(115%) contrast(98%);
}
@keyframes fadeInAnimation {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: none;
}
}

View file

@ -0,0 +1,138 @@
.container {
display: flex;
flex-wrap: wrap;
flex-direction: column;
align-items: center;
gap: 20px;
margin-top: 50px;
justify-content: space-between;
align-content: space-around;
}
.searchHeader {
display: flex;
flex-direction: row;
gap: 10px;
margin-top: 20px;
}
.searchInput {
min-width: 300px;
}
.results {
position: relative;
background-color: #fff;
min-width: 600px;
min-height: 200px;
border-radius: 8px;
color: #0f2832;
overflow: hidden;
}
.container input {
border: 1px solid #fff;
background-color: white;
padding: 8px 16px;
background-color: #fff;
border-radius: 8px;
border-color: #d9dbe1;
color: #0f2832;
width: 100%;
display: block;
height: 40px;
font-size: 12px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
transition: background-color 0.2s, box-shadow 0.2s;
caret-color: #4ed9b2;
}
.container input:hover,
.container input:focus,
.container input:active,
.container input:focus-visible {
border-color: #4ed9b2;
outline: #4ed9b2;
}
.container input:disabled {
pointer-events: none;
border: 1px solid #d9dbe1;
color: #a2a3a8;
}
.container button {
align-items: center;
border-radius: 8px;
color: #0f2832;
cursor: pointer;
font-size: 12px;
font-weight: 400;
line-height: 19px;
min-height: 40px;
min-width: 80px;
padding: 6px 20px;
position: relative;
overflow: hidden;
transition: background-color 0.5s;
background-color: #efb700;
}
.container button:hover,
.container button:focus,
.container button:active {
background-color: #fddc7b;
}
.errorText {
font-size: 12px;
text-align: center;
padding: 20px;
color: #ed6f6c;
}
@keyframes shake {
0% {
transform: translateX(0);
}
25% {
transform: translateX(2px);
}
50% {
transform: translateX(0px);
}
75% {
transform: translateX(-2px);
}
100% {
transform: translateX(0);
}
}
.invalid {
animation: shake 0.2s ease-in-out 0s 2;
}
.noItems {
padding: 20px;
text-align: center;
margin-top: 20px;
color: #a2a3a8;
}
.clearSearch {
background-color: transparent !important;
color: #efb700 !important;
border-radius: 4px !important;
margin: 0 auto;
display: block;
text-align: center;
min-height: 20px !important;
margin-top: 20px;
}
.clearSearch:hover {
color: #fddc7b !important;
}

View file

@ -0,0 +1,52 @@
.holder {
position: absolute;
width: 100%;
height: 100%;
text-align: center;
padding: 50px;
background-color: rgba(255, 255, 255, 0);
z-index: 1;
border-radius: 8px;
}
.l1 {
animation: load7 1s infinite ease-in-out;
}
.l2 {
animation: load7 1s infinite ease-in-out;
animation-delay: 0.13s;
}
.l3 {
animation: load7 1s infinite ease-in-out;
animation-delay: 0.35s;
}
.loader {
display: inline-block;
color: #4b4821;
font-size: 8px;
width: 14px;
height: 14px;
background-color: #9a9c9f;
transform: scale(0);
text-indent: -9999em;
border-radius: 50%;
}
@keyframes load7 {
0% {
transform: scale(0);
}
50% {
transform: scale(1.1);
}
60% {
transform: scale(0.8);
}
80% {
transform: scale(0);
}
100% {
transform: scale(0);
}
}

View file

@ -0,0 +1,168 @@
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
html,
body {
background-color: #0f2832;
}
body {
min-width: 768px;
line-height: 1;
font-family: 'Rubik', sans-serif;
color: #ffffff;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
button {
cursor: pointer;
border: 0;
font-family: inherit;
background-color: transparent;
}
a {
color: #4ed9b2;
text-decoration: none;
}
mark {
background-color: transparent;
}
input {
font-family: inherit;
}
h4 {
font-size: 26px;
line-height: 32px;
font-weight: 600;
}
h3 {
font-size: 24px;
line-height: 32px;
font-weight: 500;
}
h2 {
font-size: 20px;
line-height: 30px;
font-weight: 400;
}
h1 {
font-size: 16px;
line-height: 20px;
font-weight: 300;
}
* {
box-sizing: border-box;
}

View file

@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}