From 188b0534054387cad14c28d73497414a5aa4675c Mon Sep 17 00:00:00 2001 From: AshGw Date: Fri, 16 Feb 2024 23:54:03 +0100 Subject: [PATCH 1/6] feat: add docker syntax --- next.config.js | 19 -- .../blogs/redis-instances-local-testing.mdx | 289 ++++++++++++++++++ .../components/reusables/code/code-block.tsx | 2 + 3 files changed, 291 insertions(+), 19 deletions(-) create mode 100644 public/blogs/redis-instances-local-testing.mdx diff --git a/next.config.js b/next.config.js index c17bd50a..5e42b775 100644 --- a/next.config.js +++ b/next.config.js @@ -26,25 +26,6 @@ const nextConfig = { module.exports = nextConfig; const securityHeaders = [ - { - key: 'Content-Security-Policy', - value: ` - frame-ancestors 'none'; - default-src 'self'; - img-src 'self' blob: data: avatars.githubusercontent.com; - script-src 'self'; - font-src 'self'; - style-src 'self'; - media-src 'none'; - connect-src 'self'; - base-uri 'self'; - form-action 'self'; - frame-src 'self' *.codesandbox.io *.x.com; - block-all-mixed-content; - upgrade-insecure-requests; - object-src 'none'; - `, - }, { key: 'Referrer-Policy', value: 'no-referrer, strict-origin-when-cross-origin', diff --git a/public/blogs/redis-instances-local-testing.mdx b/public/blogs/redis-instances-local-testing.mdx new file mode 100644 index 00000000..4e7c183a --- /dev/null +++ b/public/blogs/redis-instances-local-testing.mdx @@ -0,0 +1,289 @@ +--- +title: Redis +seoTitle: Redis Local Containerized Testing Approaches for Rate Limiting with FastAPI +summary: Redis Local Containerized Testing Approaches (Rate Limiting) +isReleased: true +isSequel: false +firstModDate: 2022-08-12T09:15:00-0400 +lastModDate: 2022-08-12T09:15:00-0400 +minutesToRead: 4 +tags: + - 'redis' + - 'docker' + - 'fastapi' +--- + +

Problem

+ + So, you're using Redis to keep your API's rate in check and you're trying to + test your app logic using local instances + +

Solution

+ + I'll be using Python for this demo with FastAPI as a framework + + pre-requisites: -/- Python -/- Docker & Compose + + Here's the basic API endpoints we want limited + + + +where if you curl any of these endpoints once a minute you should get + +Whereas if you curl any of these endpoints twice a minute you should get + + + +However if you curl any of these endpoints twice a minute you should get + + + For the rate limiting logic, we'll use **`fastapi-limiter`**. Though you can + roll out your own, the core concept remains the same. + +Here's a raw dogged setup for some fake service + raio.Redis: + return await raio.from_url( + url=f"redis://{RedisData.USERNAME}:{RedisData.PASSWORD}@localhost:{RedisData.PORT}", + encoding="utf-8", + decode_responses=True, + ) +@asynccontextmanager +async def on_start(_app: FastAPI) -> AsyncGenerator[None, None]: + await FastAPILimiter.init(await redis_connection()) + yield + await FastAPILimiter.close() +async def limiter_dependency(req: Request, res: Response) -> None: + if req.url.path in RATE_LIMITED_PATHS: + await RateLimiter(times=2, minutes=1).__call__(req, res) +api = FastAPI( + dependencies=[Depends(limiter_dependency)], + lifespan=on_start, +) +for path in RATE_LIMITED_PATHS: + @api.get(path, tags=["rate_limited"]) + def rate_limited() -> JSONResponse: + return JSONResponse({"detail":"you'll see me once a minute"}) +if __name__ == "__main__": + run(api, host="0.0.0.0", port=PORT) +`} + language="python" + showLineNumbers={false} +/> +The current project structure: + + +here's the **`redis.conf`** file + + + + This is the most basic config you can use for testing purposes. you can read + more about all the config options + here + + and to actually run it we need a Dockerfile: + + + /sys/kernel/mm/transparent_hugepage/enabled" > /etc/rc.local \ + +&& chmod +x /etc/rc.local + +# resolve latency memory issues + +RUN echo "vm.overcommit_memory = 1" >> /etc/sysctl.conf \ + && cat /etc/sysctl.conf + +CMD ["redis-server", "redis.conf"] +`} +language="docker" +showLineNumbers={false} +/> + +you can run this directly with docker but I prefer to use compose instead + + +if your run compose now with + + + + You'll have Redis running, but how do you coordinate with the app you're + trying to run? To ensure it only starts when Redis is up and running, one way + is to curl the Redis server. If the curl operation is successful, proceed to + run the app and conduct tests, though attempting to curl Redis will likely + result in receiving + + GET / HTTP/1.1 +> Host: localhost:40185 +> User-Agent: curl/7.81.0 +> Accept: */* +> +* Received HTTP/0.9 when not allowed +* Closing connection +curl: (1) Received HTTP/0.9 when not allowed +`} + language="bash" + showLineNumbers={false} +/> + + To work around this, you could either run your API with Compose as well. + However, for testing purposes, this might be totally unnecessary. Another + workaround is to have a service act as a trigger that you can curl. This + service depends on Redis, ensuring that it only runs if Redis is functioning + correctly. An example of such a service could be based on NGINX. The modified + **`compose.yaml`** file is updated as follows: + + +now if you run compose again: + +Use this bash script to test your service +/dev/null; do + sleep 1 +done +python -m api & +server_pid=$! +## +pytest +## +kill $server_pid +docker-compose down +`} + language="bash" + showLineNumbers={false} +/> + + This script orchestrates the Docker Compose setup, waits for the NGINX trigger + to be accessible, runs your FastAPI app, executes tests, shuts down the + server, and tears down the Docker containers. You can implement your own + testing logic , using any test runner. + diff --git a/src/app/components/reusables/code/code-block.tsx b/src/app/components/reusables/code/code-block.tsx index cfc38fc2..3d1cb8ec 100644 --- a/src/app/components/reusables/code/code-block.tsx +++ b/src/app/components/reusables/code/code-block.tsx @@ -12,6 +12,7 @@ import go from 'react-syntax-highlighter/dist/cjs/languages/prism/go'; import bash from 'react-syntax-highlighter/dist/cjs/languages/prism/bash'; import css from 'react-syntax-highlighter/dist/cjs/languages/prism/css'; import tsx from 'react-syntax-highlighter/dist/cjs/languages/prism/tsx'; +import docker from 'react-syntax-highlighter/dist/cjs/languages/prism/docker'; import oneDark from 'react-syntax-highlighter/dist/cjs/styles/prism/one-dark'; import CopyButton from './copy-code'; import { Skeleton } from '../../ui/skeleton'; @@ -24,6 +25,7 @@ SyntaxHighlighter.registerLanguage('typescript', typescript); SyntaxHighlighter.registerLanguage('go', go); SyntaxHighlighter.registerLanguage('tsx', tsx); SyntaxHighlighter.registerLanguage('css', css); +SyntaxHighlighter.registerLanguage('docker', docker); export type CodeBlockProps = { language: string; From e8fad672279ea3e5961bd01bc18f48074ca4254f Mon Sep 17 00:00:00 2001 From: AshGw Date: Sat, 17 Feb 2024 19:35:28 +0100 Subject: [PATCH 2/6] feat: have line spacing go for loose @H1 tags --- .prettierignore | 2 +- package.json | 2 + pnpm-lock.yaml | 23 +++ public/blogs/CSRF-mitigation-techniques.mdx | 13 ++ public/blogs/captionize.mdx | 81 -------- public/blogs/framer-motion.mdx | 21 +- public/blogs/gsap-just-got-better.mdx | 13 ++ public/blogs/privacy-policy-template.mdx | 94 --------- .../blogs/redis-instances-local-testing.mdx | 181 ++++++++++++++---- public/blogs/simple.mdx | 87 --------- public/blogs/tester.mdx | 178 ----------------- src/app/(pages)/x/page.tsx | 28 ++- .../components/reusables/code/code-block.tsx | 2 + src/app/components/reusables/headers.tsx | 2 +- src/lib/types/global.ts | 8 +- 15 files changed, 243 insertions(+), 492 deletions(-) create mode 100644 public/blogs/CSRF-mitigation-techniques.mdx delete mode 100644 public/blogs/captionize.mdx create mode 100644 public/blogs/gsap-just-got-better.mdx delete mode 100644 public/blogs/privacy-policy-template.mdx delete mode 100644 public/blogs/simple.mdx delete mode 100644 public/blogs/tester.mdx diff --git a/.prettierignore b/.prettierignore index b43267ed..1d9e089a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,6 @@ # local env files .env*.local - +*.mdx # dependencies /node_modules /.pnp diff --git a/package.json b/package.json index e78f9860..8feae7a8 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "webpack": "^5.90.1" }, "dependencies": { + "@gsap/react": "^2.1.0", "@nextui-org/react": "^2.2.9", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-separator": "^1.0.3", @@ -57,6 +58,7 @@ "dotenv": "^16.3.1", "framer-motion": "^10.17.4", "front-matter": "^4.0.2", + "gsap": "^3.12.5", "lucide-react": "^0.309.0", "next": "^14.0.4", "next-mdx-remote": "^4.4.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc39bb55..c2e404b1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + '@gsap/react': + specifier: ^2.1.0 + version: 2.1.0 '@nextui-org/react': specifier: ^2.2.9 version: 2.2.9(@types/react@18.2.55)(framer-motion@10.18.0)(react-dom@18.2.0)(react@18.2.0)(tailwind-variants@0.1.20)(tailwindcss@3.4.1) @@ -32,6 +35,9 @@ dependencies: front-matter: specifier: ^4.0.2 version: 4.0.2 + gsap: + specifier: ^3.12.5 + version: 3.12.5 lucide-react: specifier: ^0.309.0 version: 0.309.0(react@18.2.0) @@ -1012,6 +1018,16 @@ packages: tslib: 2.6.2 dev: false + /@gsap/react@2.1.0: + resolution: + { + integrity: sha512-pwdFXvOM5IsRZXpWTKkQoEjb3/iUjDCU1BCJDlE6pHgVjG+7Ep/7+sszUgqVZ2Jc0mR8gnhtDWyx5cQAT4kwQw==, + } + dependencies: + gsap: 3.12.5 + react: 18.2.0 + dev: false + /@humanwhocodes/config-array@0.11.14: resolution: { @@ -8424,6 +8440,13 @@ packages: } dev: true + /gsap@3.12.5: + resolution: + { + integrity: sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ==, + } + dev: false + /handlebars@4.7.8: resolution: { diff --git a/public/blogs/CSRF-mitigation-techniques.mdx b/public/blogs/CSRF-mitigation-techniques.mdx new file mode 100644 index 00000000..9579b86f --- /dev/null +++ b/public/blogs/CSRF-mitigation-techniques.mdx @@ -0,0 +1,13 @@ +--- +title: CSRF Mitigation +seoTitle: No BS guide over how to actually do it to the most +summary: No BS guide over how to actually do it to the most +isReleased: true +isSequel: false +lastModDate: 2020-04-19T09:15:00-0401 +firstModDate: 2020-04-19T09:15:00-0401 +minutesToRead: 5 +tags: + - 'react' + - 'framer-motion' +--- \ No newline at end of file diff --git a/public/blogs/captionize.mdx b/public/blogs/captionize.mdx deleted file mode 100644 index 3a7b40d8..00000000 --- a/public/blogs/captionize.mdx +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: Captionize -seoTitle: Web application that automatically generates descriptive captions for images using a Blind Language-Image Pre-training (BLIP) model. -summary: How to run an image captioning web app -isReleased: true -isSequel: false -lastModDate: 2018-04-20T09:15:00-0401 -firstModDate: 2012-04-20T09:15:00-0400 -minutesToRead: 18 -tags: - - 'blpp' - - 'ai' - - 'tensorflow' ---- - -# Captionize 📷 - -### Web application that automatically generates descriptive captions for images using a Blind Language-Image Pre-training (BLIP) model. - -## Overview 🎥 - -https://github.com/AshGw/Captionize/assets/126174609/81840a72-2b9d-4a48-a56e-e19d8f5fb14c - -## Installation 🛠️ - -To install and run the app on any machine, you can choose from two methods: - -Using Docker for isolation or, if you already have Python installed, the latter option. - -**During the installation process, you will receive instructions on how to re-run the app if needed.** - -> **NOTE: The installation may take some time as the dependencies are quite heavy.** - -### Docker 🐳 - -1. Install **[Docker](https://www.docker.com/get-started/)** If you don't have it already. -2. Next, run this command in an empty directory using your terminal or **[Git Bash](https://git-scm.com/downloads)** for windows. - -```shell -curl -sSfl https://raw.githubusercontent.com/AshGw/Captionize/main/setup/docker-auto-run.sh | bash -``` - -That's it, the app should now be up and running at: - -> _http://localhost:7854_ - -### Python 🐍 - -1. Install **[Python](https://www.python.org/downloads)** (3.10+) if you don't have it already. -2. Next, run this command in an empty directory using your terminal or **[Git Bash](https://git-scm.com/downloads)** for windows. - -```shell -curl -sSfl https://raw.githubusercontent.com/AshGw/Captionize/main/setup/install.sh | bash -``` - -This command will install the entire project, sets up the virtual environment, installs dependencies and then runs the app. - -You can view it at: - -> _http://localhost:8000_ - -## Keynote 📝 - -The app includes the model directly on your machine, which means you can use the app **offline**, no need for an internet connection. - -During the initial image captioning process, there will be a delay as additional dependencies are installed. - -
- -It should work smoothly after, if: - -- For Docker: Subsequent runs of the container. -- For Python: Keeping the same virtual environment activated. - -## License 📜 - -This project is licensed under the [MIT License](https://github.com/AshGw/Captionize/blob/main/LICENSE). - -## Acknowledgments 🙌 - -This project was made possible by the pre-trained BLIP model provided by **[Hugging Face](https://huggingface.co/Salesforce/blip-image-captioning-large).** diff --git a/public/blogs/framer-motion.mdx b/public/blogs/framer-motion.mdx index ca8e6e8d..e7a8bc14 100644 --- a/public/blogs/framer-motion.mdx +++ b/public/blogs/framer-motion.mdx @@ -1,12 +1,12 @@ --- title: Framer Motion seoTitle: Streamlining Animations with Framer Motion in React -summary: Changed the way I handle animations in React +summary: Supercharging animations in React isReleased: true isSequel: false lastModDate: 2020-04-19T09:15:00-0401 firstModDate: 2020-04-19T09:15:00-0401 -minutesToRead: 18 +minutesToRead: 5 tags: - 'react' - 'framer-motion' @@ -75,7 +75,9 @@ showLineNumbers={true} { + const [isVisible, setIsVisible] = useState(false); + return (
{ + const [isVisible, setIsVisible] = useState(false); const ref = useRef(null); + useEffect(() => { // Handle it when in view setIsVisible(!isVisible); }, [ref]); + return (
{ const [isVisible, setIsVisible] = useState(false); const ref = useRef(null); + useEffect(() => { const current = ref.current; const observer = new IntersectionObserver( @@ -171,6 +178,7 @@ const Component: React.FC = () => { } }; }, [ref]); + return (
{
); }; + export default Component; const FadingText: React.FC = () => { return ( @@ -206,6 +215,7 @@ const FadingText: React.FC = () => { code={` import React from 'react'; import { motion } from 'framer-motion'; + const FramerMotionFadeInComponent: React.FC = () => { return (
@@ -227,6 +237,7 @@ const FramerMotionFadeInComponent: React.FC = () => { code={` import React from 'react'; import { motion } from 'framer-motion'; + const FramerMotionFadeInComponent: React.FC = () => { return (
@@ -265,6 +276,7 @@ const FramerMotionFadeInComponent: React.FC = () => { Here's the code for it - ashgw ("we" or "us") is committed to protecting the privacy of our users. This - Privacy Policy outlines how we collect, use, and safeguard your personal - information. By using our services, you consent to the practices described in - this policy. Our guiding principle is to collect only what we need. Here's - what that means in practice. -
- -### **Your Information** - -
- -When you sign up for our product or service, we may request certain identifying information, such as your name and email address. This allows us to personalize your new account. - -It's important to note that none of this information is stored on our database. Instead, for the purposes of managing your plan, subscription, or purchase, we securely hash and encrypt your email address, ensuring a high level of privacy and security. - -
- -### **Authentication** - -
- -As part of our authentication process, we exclusively employ OAuth2 with trusted providers like Google. This enables us to access your name and profile image session data stored securely in your browser. Please rest assured that these specific details are not permanently stored in our database infrastructure. They serve their purpose solely for the duration of your active session on our platform. - -
- -### **Cookies** - -
- -We do use persistent first-party cookies to store certain preferences, making it easier for you to use our applications. A cookie is a piece of text stored by your browser to help it remember your login information, site preferences, and more. You can adjust cookie retention settings in your own browser. - -
- -### **User-Generated Requests** - -
- -When you use our service to generate AI images based on a word or phrase, we collect the input provided. This information is used solely to generate the requested image and is not stored or associated with your personal information. - -
- -### **Security** - -
- -All data is encrypted via SSL/TLS when transmitted from our servers to your browser. - -
- -### **Changes** - -
- -We may update this policy as needed to comply with relevant regulations and reflect any new practices. - -Should you have any questions, comments, or concerns regarding this privacy policy, your data, or your rights in regard to your information, please don't hesitate to reach out. You can [contact](/contact) us and we'll be more than happy to assist you! - -
diff --git a/public/blogs/redis-instances-local-testing.mdx b/public/blogs/redis-instances-local-testing.mdx index 4e7c183a..ae813d04 100644 --- a/public/blogs/redis-instances-local-testing.mdx +++ b/public/blogs/redis-instances-local-testing.mdx @@ -1,7 +1,7 @@ --- -title: Redis -seoTitle: Redis Local Containerized Testing Approaches for Rate Limiting with FastAPI -summary: Redis Local Containerized Testing Approaches (Rate Limiting) +title: Test Redis with Docker +seoTitle: How to use Docker to to test Redis connections without resorting to mocks +summary: A containerized approach for testing Redis connections instead resorting to mocks isReleased: true isSequel: false firstModDate: 2022-08-12T09:15:00-0400 @@ -10,19 +10,26 @@ minutesToRead: 4 tags: - 'redis' - 'docker' - - 'fastapi' + - 'compose' --- +

Problem

- So, you're using Redis to keep your API's rate in check and you're trying to - test your app logic using local instances + So, you're using Redis to keep your API's rate in check , you're probably using cloud instances for prod and you're trying to + test your app logic, the natural inclination is to mock these connections and call it a day, don't! Mocking should be reserved for situations where absolute + necessity dictates or when replication of specific conditions proves unattainable. So here's a brief solution that should be language/Framework/Lib agnostic

Solution

- I'll be using Python for this demo with FastAPI as a framework + I'll be using Python for this demo with FastAPI as a + framework and Poetry as the package manager, + but it doesn't matter, the core concept remains the same regardless of the stack being used. - pre-requisites: -/- Python -/- Docker & Compose + pre-requisites: + - \- Python + - \- Docker & Compose + Here's the basic API endpoints we want limited @@ -57,22 +64,16 @@ tags: /> -However if you curl any of these endpoints twice a minute you should get - - For the rate limiting logic, we'll use **`fastapi-limiter`**. Though you can - roll out your own, the core concept remains the same. + For the rate limiting logic, we'll use ``fastapi_limiter``. Though you can + roll out your own. -Here's a raw dogged setup for some fake service +Here's a state of the art, robust, elegant and 120% prod ready service raio.Redis: + +# You probably have some logic to switch between prod & dev environments. +async def local_redis_connection() -> raio.Redis: return await raio.from_url( url=f"redis://{RedisData.USERNAME}:{RedisData.PASSWORD}@localhost:{RedisData.PORT}", encoding="utf-8", decode_responses=True, ) + + @asynccontextmanager -async def on_start(_app: FastAPI) -> AsyncGenerator[None, None]: - await FastAPILimiter.init(await redis_connection()) +async def lifespan(_app: FastAPI) -> AsyncGenerator[None, None]: + await FastAPILimiter.init(await local_redis_connection()) # run when: app starts yield - await FastAPILimiter.close() + await FastAPILimiter.close() # run when: app shuts down + + async def limiter_dependency(req: Request, res: Response) -> None: if req.url.path in RATE_LIMITED_PATHS: await RateLimiter(times=2, minutes=1).__call__(req, res) + + api = FastAPI( dependencies=[Depends(limiter_dependency)], - lifespan=on_start, + lifespan=lifespan, ) + + for path in RATE_LIMITED_PATHS: @api.get(path, tags=["rate_limited"]) def rate_limited() -> JSONResponse: return JSONResponse({"detail":"you'll see me once a minute"}) + + if __name__ == "__main__": - run(api, host="0.0.0.0", port=PORT) + run(api, host="0.0.0.0", port=8000) `} language="python" showLineNumbers={false} @@ -131,11 +148,12 @@ if __name__ == "__main__": code={`. ├── api.py ├── compose.yaml +├── .env ├── poetry.lock ├── pyproject.toml ├── redis.conf ├── redis.dockerfile -├── test.sh +├── test # shell script └── test.py `} language="bash" @@ -159,7 +177,7 @@ protected-mode no more about all the config options here - and to actually run it we need a Dockerfile: + To actually run it we need a Dockerfile: -you can run this directly with docker but I prefer to use compose instead +you can run this directly with Docker but I prefer to use **``docker-compose``** instead + now if you run compose again: -Use this bash script to test your service +Use this **``test``** script to test your service - This script orchestrates the Docker Compose setup, waits for the NGINX trigger - to be accessible, runs your FastAPI app, executes tests, shuts down the - server, and tears down the Docker containers. You can implement your own - testing logic , using any test runner. + This script orchestrates the **Docker-Compose** configs, runs it in the background, + waits for the **NGINX** trigger + to be accessible, runs the **FastAPI** app, executes tests, shuts down the + server, and tears down the **Docker** containers. + +You can implement your own testing logic , using any test runner. + +Now since I'm using **FastAPI** I will omit the server running step from the test script +, as **FastAPI** provides a **``TestClient``** class you can use to test your app logic +without having to actually run the server. Also if you're using **``Pytest``** you can skip/ignore tests if a +condition is not met, in this case, if no conatiners are spun up, skip. The idea might be like + +If you use **Go** for example an abstract setup might be like + + +But the idea remains the same, scale redis container(s) before tests and scale down and clean up after, the only part changing in the script +is the test runner, instead of Python's **``pyest``** it'll be **`go test`**, if you use rust then it's **`cargo test`** and so on... \ No newline at end of file diff --git a/public/blogs/simple.mdx b/public/blogs/simple.mdx deleted file mode 100644 index 0d3e4b0a..00000000 --- a/public/blogs/simple.mdx +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: Simple -seoTitle: Simple yapping nonetheless -summary: Simple yapping nonetheless -isReleased: true -isSequel: false -lastModDate: 2015-04-20T09:15:00-0400 -firstModDate: 2015-04-20T09:15:00-0400 -minutesToRead: 30 -tags: - - 'journey' - - 'career' ---- - -

Server

- - First you might want to yeet the **`Server`** & **`X-Powered-By`** headers (the latter being - unofficial), preferably through the server configs, or check `no_srv.go`. I have metioned this actually here before - -> _Security through obscurity ain’t all that chief, but it's just a good complementary security measure._ - -To test that you've removed this entirely you can use curl: - - - - If youd like to preserve the default values for a theme option but - also add new values, add your extensions under the theme.extend key in - your configuration file. Values under this key are merged with - existing theme values and automatically become available as new - classes that you can use. As an example, here we extend the fontFamily - property to add the font-display class that can change the font used - on an element: If youd like to preserve the default values for a theme - option but also add new values, add your extensions under the - theme.extend key in your configuration file. Values under this key are - merged with existing theme values and automatically become available - as new classes that you can use. As an example, here we extend the - fontFamily property to add the font-display class that can change the - font used on an element: If youd like to preserve the default values - for a theme option but also add new values, add your extensions under - the theme.extend key in your configuration file. - - Values under this key - are merged with existing theme values and automatically become - available as new classes that you can use. As an example, here we - extend the fontFamily property to add the font-display class that can - change the font used on an element: If youd like to preserve the - default values for a theme option but also add new values, add your - extensions under the theme.extend key in your configuration file. - Values under this key are merged with existing theme values and - automatically become available as new classes that you can use. As an - example, here we extend the fontFamily property to add the - font-display class that can change the font used on an element: If - youd like to preserve the default values for a theme option but also - add new values, add your extensions under the theme.extend key in your - configuration file. Values under this key are merged with existing - theme values and automatically become available as new classes that - you can use. As an example, here we extend the fontFamily property to - add the font-display class that can change the font used on an - element: If youd like to preserve the default values for a theme - option but also add new values, add your extensions under the - theme.extend key in your configuration file. Values under this key are - merged with existing theme values and automatically become available - as new classes that you can use. As an example, here we extend the - fontFamily property to add the font-display class that can change the - font used on an element: If youd like to preserve the default values - for a theme option but also add new values, add your extensions under - the theme.extend key in your configuration file. - - - Values under this key - are merged with existing theme values and automatically become - available as new classes that you can use. As an example, here we - extend the fontFamily property to add the font-display class that can - change the font used on an element: If youd like to preserve the - default values for a theme option but also add new values, add your - extensions under the theme.extend key in your configuration file. - Values under this key are merged with existing theme values and - automatically become available as new classes that you can use. As an - example, here we extend the fontFamily property to add the - font-display class that can change the font used on an element: If - youd like to preserve the default values for a theme option but also - add new values, add your extensions under the theme.extend key in your - configuration file. Values under this key are merged with existing - theme values and automatically become available as new classes that - you can use. As an example, here we extend the fontFamily property to - add the font-display class that can change the font used on an - diff --git a/public/blogs/tester.mdx b/public/blogs/tester.mdx deleted file mode 100644 index d3307cbb..00000000 --- a/public/blogs/tester.mdx +++ /dev/null @@ -1,178 +0,0 @@ ---- -title: Tester -seoTitle: This is how to migrate from vercel to AWS -summary: This is how to migrate from vercel to AWS -isReleased: true -isSequel: false -lastModDate: 2024-02-20T09:15:00-0400 -firstModDate: 2024-02-20T09:15:00-0400 -minutesToRead: 13 -tags: - - 'aws' - - 'vercel' - - 'serverless' ---- - -

Privacy Policy

- - First we might talk about how to run in parallel and run the app on any machine, you can choose from two methods: - -Using **``getStaticQueryParams()``** for isolation or, you can use Google if already have Python installed, the latter option. -**During the installation process, you will receive instructions on how to re-run the app if needed, one can mention:** - -* \- **Referrer**: Static site generation - - -- \- **Oppenheimer**: Beatle public asssessment - - {' '} - - {' '} - - - -- \- **Crunch**: Shuttle is new - - -> > _NOTE: The installation may take some time as the dependencies are quite heavy._ - - -, -} -`} -language="rust" -showLineNumbers={true} - -> - -

Your Information

- - When you sign up for our product or service, we may request certain - identifying information, such as your name and email address. This allows us - to personalize your new account. It's important to note that none of this - information is stored in our database. Instead, for the purposes of managing - your plan, subscription, or purchase, we securely hash and encrypt your email - address, ensuring a high level of privacy and security. - - None: - with patch( - "fastauth.providers.google.google.Google._request_access_token" - ) as mock_token_request: - mock_response = AsyncMock() - mock_response.status_code = StatusCode.OK - mock_response.json = valid_token_response - mock_token_request.return_value = mock_response - _result = await google._request_access_token( - state=op.state, - code_verifier=op.code_verifier, - code="...", - ) -`} -language="python" -showLineNumbers={true} - -> - - - - - -

Authentication

- - As part of our authentication process, we exclusively employ OAuth2 with trusted providers like Google. This enables us to access your name and profile image session data stored securely in your browser. Please rest assured that these specific details are not permanently stored in our database infrastructure. They serve their purpose solely for the duration of your active session on our platform. - -

Cookies

- - We do use persistent first-party cookies to store certain preferences, making it easier for you to use our applications. A cookie is a piece of text stored by your browser to help it remember your login information, site preferences, and more. You can adjust cookie retention settings in your own browser. - -

User-Generated Requests

- - When you use our service to generate AI images based on a word or phrase, we collect the input provided. This information is used solely to generate the requested image and is not stored or associated with your personal information. - -

Security

- -Loading video...

}> - - - ) -} - -async function VideoComponent({ fileName }) { - const { blobs } = await list({ - prefix: fileName, - limit: 1, - }) - const { url } = blobs[0] - - return ( - - ) -}`} - language="typescript" - showLineNumbers={true} - copy={false} -> - {' '} -
- - All data is encrypted via SSL/TLS when transmitted from our servers to your - browser. - - -

Changes

- - We may update this policy as needed to comply with relevant regulations and reflect any new practices. - Should you have any questions, comments, or concerns regarding this privacy policy, your data, or your rights in regard to your information, please don't hesitate to reach out. You can contact us and we'll be more than happy to assist you! - diff --git a/src/app/(pages)/x/page.tsx b/src/app/(pages)/x/page.tsx index 73cb30c1..64ad1d56 100644 --- a/src/app/(pages)/x/page.tsx +++ b/src/app/(pages)/x/page.tsx @@ -1,10 +1,26 @@ -import React from 'react'; +'use client'; +import { useRef } from 'react'; +import LightGrid from '@/app/components/__futures__/light-grid'; +import gsap from 'gsap'; +import { useGSAP } from '@gsap/react'; + +export default function App() { + const container = useRef(null); + + useGSAP( + () => { + const tl = gsap.timeline(); + tl.to('.box', { rotation: 180, duration: 2 }); + }, + { scope: container } + ); -export default function SplitCard() { return ( - <> - Lorem ipsum dolor, sit amet consectetur adipisicing elit. Amet hic - voluptatum hic omnis laudantium accusamus. - +
+
+
Hello
+
+ +
); } diff --git a/src/app/components/reusables/code/code-block.tsx b/src/app/components/reusables/code/code-block.tsx index 3d1cb8ec..7d5dfe45 100644 --- a/src/app/components/reusables/code/code-block.tsx +++ b/src/app/components/reusables/code/code-block.tsx @@ -13,6 +13,7 @@ import bash from 'react-syntax-highlighter/dist/cjs/languages/prism/bash'; import css from 'react-syntax-highlighter/dist/cjs/languages/prism/css'; import tsx from 'react-syntax-highlighter/dist/cjs/languages/prism/tsx'; import docker from 'react-syntax-highlighter/dist/cjs/languages/prism/docker'; +import yaml from 'react-syntax-highlighter/dist/cjs/languages/prism/yaml'; import oneDark from 'react-syntax-highlighter/dist/cjs/styles/prism/one-dark'; import CopyButton from './copy-code'; import { Skeleton } from '../../ui/skeleton'; @@ -26,6 +27,7 @@ SyntaxHighlighter.registerLanguage('go', go); SyntaxHighlighter.registerLanguage('tsx', tsx); SyntaxHighlighter.registerLanguage('css', css); SyntaxHighlighter.registerLanguage('docker', docker); +SyntaxHighlighter.registerLanguage('yaml', yaml); export type CodeBlockProps = { language: string; diff --git a/src/app/components/reusables/headers.tsx b/src/app/components/reusables/headers.tsx index e7e98887..edbbc339 100644 --- a/src/app/components/reusables/headers.tsx +++ b/src/app/components/reusables/headers.tsx @@ -23,7 +23,7 @@ export function Heading1({ children, id }: HeadingProps) { ease: 'easeInOut', }} id={id} - className="font-extrabold my-2 text-[2.5rem]" + className=" leading-10 font-extrabold my-2 text-[2.5rem]" > {children} diff --git a/src/lib/types/global.ts b/src/lib/types/global.ts index 87f45e3f..b7099d0a 100644 --- a/src/lib/types/global.ts +++ b/src/lib/types/global.ts @@ -3,11 +3,11 @@ import { NextResponse } from 'next/server'; export type Optional = T | null; export type Maybe = T | undefined; -export interface Err { +export type Err = { error: string; -} -export interface Ok { +}; +export type Ok = { data: T; -} +}; export type AsyncResponse = Promise> | NextResponse>; From 5a729ec10378e3d8aae5285efb3f181e24627ccf Mon Sep 17 00:00:00 2001 From: AshGw Date: Sat, 17 Feb 2024 22:04:33 +0100 Subject: [PATCH 3/6] feat: add `matchURL()` to parse sequential blogs --- src/app/(pages)/x/page.tsx | 31 +++++++++++++++- src/app/components/__futures__/light-grid.tsx | 35 ++++++++++++++++--- src/app/styles/custom.css | 2 +- src/lib/funcs/parsers.ts | 5 +++ src/lib/funcs/sequel-blogs-redirection.ts | 30 ++++++++++++++++ test/lib/sequel-blogs-redirection.test.ts | 22 ++++++++++++ 6 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 src/lib/funcs/parsers.ts create mode 100644 src/lib/funcs/sequel-blogs-redirection.ts create mode 100644 test/lib/sequel-blogs-redirection.test.ts diff --git a/src/app/(pages)/x/page.tsx b/src/app/(pages)/x/page.tsx index 64ad1d56..7d5e67e0 100644 --- a/src/app/(pages)/x/page.tsx +++ b/src/app/(pages)/x/page.tsx @@ -4,13 +4,20 @@ import LightGrid from '@/app/components/__futures__/light-grid'; import gsap from 'gsap'; import { useGSAP } from '@gsap/react'; +import { ChevronRight, ChevronLeft } from 'lucide-react'; + export default function App() { const container = useRef(null); useGSAP( () => { const tl = gsap.timeline(); - tl.to('.box', { rotation: 180, duration: 2 }); + tl.to('.box', { + rotation: 360, + duration: 2, + repeat: -1, + ease: 'expo', + }); }, { scope: container } ); @@ -20,7 +27,29 @@ export default function App() {
Hello
+
+
); } + +function NextArticle() { + return ( + <> + + + ); +} diff --git a/src/app/components/__futures__/light-grid.tsx b/src/app/components/__futures__/light-grid.tsx index c877b10f..596db491 100644 --- a/src/app/components/__futures__/light-grid.tsx +++ b/src/app/components/__futures__/light-grid.tsx @@ -1,15 +1,39 @@ -export default function LightGrid() { +'use client'; +import gsap from 'gsap'; +import { useGSAP } from '@gsap/react'; +import { useRef } from 'react'; + +const LightGrid: React.FC = () => { + const ref = useRef(null); + useGSAP( + () => { + const tl = gsap.timeline(); + tl.to('dxd', { + duration: 1, + scale: 0.1, + y: 40, + ease: 'power1.inOut', + stagger: { + grid: [10, 10], + from: 'center', + amount: 1.5, + }, + }); + }, + { scope: ref } + ); + return ( <>
-
-
+
+
{Array.from({ length: 100 }, (_, index) => (
- +
))}
@@ -18,4 +42,5 @@ export default function LightGrid() {
); -} +}; +export default LightGrid; diff --git a/src/app/styles/custom.css b/src/app/styles/custom.css index 9cb132a3..0758d547 100644 --- a/src/app/styles/custom.css +++ b/src/app/styles/custom.css @@ -60,7 +60,7 @@ } .turn-bg-gradient::after { - /*If i change my mind abouut the theme I'll use this for inidividual components*/ + /*If I change my mind about the theme I'll use this for individual components*/ background-image: linear-gradient( 84.16deg, rgba(150, 46, 200, 0.15), diff --git a/src/lib/funcs/parsers.ts b/src/lib/funcs/parsers.ts new file mode 100644 index 00000000..0f8b1341 --- /dev/null +++ b/src/lib/funcs/parsers.ts @@ -0,0 +1,5 @@ +import type { Optional } from '@/lib/types/global'; + +export function matchURL(url: string): Optional { + return url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/.*)?$/); // eslint-disable-line no-useless-escape +} diff --git a/src/lib/funcs/sequel-blogs-redirection.ts b/src/lib/funcs/sequel-blogs-redirection.ts new file mode 100644 index 00000000..8f9140c7 --- /dev/null +++ b/src/lib/funcs/sequel-blogs-redirection.ts @@ -0,0 +1,30 @@ +import { matchURL } from './parsers'; +export type Where = 'next' | 'previous'; + +/* + MDX blogs that have the metadata attribute `isSequel` should have + the format ^.*-part-(\d+).mdx for this to work properly, +*/ +export function whereToGo(href: string, where: Where): string { + if (!matchURL(href)) { + return '#'; + } + const match = href.match(/-part-(\d+)/); + if (match) { + let goTo = parseInt(match[1]) + 1; + if (where === 'previous') { + goTo = goTo - 2; + } + return href.replace(regex, `part-${goTo}`); + } + return '#'; +} + +console.log(whereToGo('http://localhost-part-5', 'previous')); // http://localhostpart-4 +console.log(whereToGo('http://localhost-part-5', 'next')); // http://localhostpart-6 +console.log(whereToGo('https://localhost-part-69', 'previous')); // # https://localhostpart-68 +console.log(whereToGo('http://localhost-part98', 'previous')); // # +console.log(whereToGo('http//localhost-part98', 'previous')); // # +console.log(whereToGo('ht-art-5', 'previous')); // # +console.log(whereToGo('httppart5', 'next')); // # +console.log(whereToGo('httppart-5', 'next')); // # diff --git a/test/lib/sequel-blogs-redirection.test.ts b/test/lib/sequel-blogs-redirection.test.ts new file mode 100644 index 00000000..f05a11fa --- /dev/null +++ b/test/lib/sequel-blogs-redirection.test.ts @@ -0,0 +1,22 @@ +import { whereToGo } from '@/lib/funcs/sequel-blogs-redirection'; + +describe('whereToGo', () => { + it('should generate the URL for the previous blog post', () => { + expect(whereToGo('http://localhost-part-5', 'previous')).toBe( + 'http://localhostpart-4' + ); + }); + + it('should generate the URL for the next blog post', () => { + expect(whereToGo('http://localhost-part-5', 'next')).toBe( + 'http://localhostpart-6' + ); + }); + + it('should return "#" for invalid URLs (no match)', () => { + expect(whereToGo('http://localhost-part98', 'previous')).toBe('#'); + expect(whereToGo('ht-art-5', 'previous')).toBe('#'); + expect(whereToGo('httppart5', 'next')).toBe('#'); + expect(whereToGo('httppart-5', 'next')).toBe('#'); + }); +}); From 6844fc222dafca6b52382c8c35f9982001956892 Mon Sep 17 00:00:00 2001 From: AshGw Date: Sat, 17 Feb 2024 22:52:58 +0100 Subject: [PATCH 4/6] test: does testing for matchers --- src/lib/funcs/matchers.ts | 9 ++++++++ src/lib/funcs/parsers.ts | 5 ----- src/lib/funcs/sequel-blogs-redirection.ts | 14 ++++-------- src/lib/funcs/site-name.ts | 3 ++- test/lib/sequel-blogs-redirection.test.ts | 10 +++++++-- test/lib/url-matcher.test.ts | 27 +++++++++++++++++++++++ 6 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 src/lib/funcs/matchers.ts delete mode 100644 src/lib/funcs/parsers.ts create mode 100644 test/lib/url-matcher.test.ts diff --git a/src/lib/funcs/matchers.ts b/src/lib/funcs/matchers.ts new file mode 100644 index 00000000..8bf4afa3 --- /dev/null +++ b/src/lib/funcs/matchers.ts @@ -0,0 +1,9 @@ +import type { Optional } from '@/lib/types/global'; + +export const URL_REGEX = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}$/; // eslint-disable-line no-useless-escape +export const OPTIONAL_PROTOCOL_URL_REGEX = + /^(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/.*)?$/; // eslint-disable-line no-useless-escape + +export function matchURL(url: string): Optional { + return url.match(URL_REGEX); +} diff --git a/src/lib/funcs/parsers.ts b/src/lib/funcs/parsers.ts deleted file mode 100644 index 0f8b1341..00000000 --- a/src/lib/funcs/parsers.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { Optional } from '@/lib/types/global'; - -export function matchURL(url: string): Optional { - return url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/.*)?$/); // eslint-disable-line no-useless-escape -} diff --git a/src/lib/funcs/sequel-blogs-redirection.ts b/src/lib/funcs/sequel-blogs-redirection.ts index 8f9140c7..bb3be7eb 100644 --- a/src/lib/funcs/sequel-blogs-redirection.ts +++ b/src/lib/funcs/sequel-blogs-redirection.ts @@ -1,4 +1,5 @@ -import { matchURL } from './parsers'; +import { matchURL } from './matchers'; + export type Where = 'next' | 'previous'; /* @@ -9,7 +10,8 @@ export function whereToGo(href: string, where: Where): string { if (!matchURL(href)) { return '#'; } - const match = href.match(/-part-(\d+)/); + const regex: RegExp = /-part-(\d+)/; + const match: RegExpMatchArray | null = href.match(regex); if (match) { let goTo = parseInt(match[1]) + 1; if (where === 'previous') { @@ -20,11 +22,3 @@ export function whereToGo(href: string, where: Where): string { return '#'; } -console.log(whereToGo('http://localhost-part-5', 'previous')); // http://localhostpart-4 -console.log(whereToGo('http://localhost-part-5', 'next')); // http://localhostpart-6 -console.log(whereToGo('https://localhost-part-69', 'previous')); // # https://localhostpart-68 -console.log(whereToGo('http://localhost-part98', 'previous')); // # -console.log(whereToGo('http//localhost-part98', 'previous')); // # -console.log(whereToGo('ht-art-5', 'previous')); // # -console.log(whereToGo('httppart5', 'next')); // # -console.log(whereToGo('httppart-5', 'next')); // # diff --git a/src/lib/funcs/site-name.ts b/src/lib/funcs/site-name.ts index 6ba3ff6c..d37c1e50 100644 --- a/src/lib/funcs/site-name.ts +++ b/src/lib/funcs/site-name.ts @@ -1,7 +1,8 @@ import { Optional } from '../types/global'; +import { OPTIONAL_PROTOCOL_URL_REGEX } from './matchers'; export function getSiteName(url: string): Optional { - const match = url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/.*)?$/); // eslint-disable-line no-useless-escape + const match = url.match(OPTIONAL_PROTOCOL_URL_REGEX); if (match) { const parts = match[1].split('.'); const tld = parts.pop(); diff --git a/test/lib/sequel-blogs-redirection.test.ts b/test/lib/sequel-blogs-redirection.test.ts index f05a11fa..b4c82016 100644 --- a/test/lib/sequel-blogs-redirection.test.ts +++ b/test/lib/sequel-blogs-redirection.test.ts @@ -12,9 +12,15 @@ describe('whereToGo', () => { 'http://localhostpart-6' ); }); - - it('should return "#" for invalid URLs (no match)', () => { + it('should generate the URL for the next blog post with https', () => { + expect(whereToGo('https://localhost-part-5', 'next')).toBe( + 'https://localhostpart-6' + ); + }); + it('should return "#" for invalid matches', () => { expect(whereToGo('http://localhost-part98', 'previous')).toBe('#'); + expect(whereToGo('http:/localhost-part98', 'previous')).toBe('#'); + expect(whereToGo('https://localhost-part98', 'previous')).toBe('#'); expect(whereToGo('ht-art-5', 'previous')).toBe('#'); expect(whereToGo('httppart5', 'next')).toBe('#'); expect(whereToGo('httppart-5', 'next')).toBe('#'); diff --git a/test/lib/url-matcher.test.ts b/test/lib/url-matcher.test.ts new file mode 100644 index 00000000..c0d5a483 --- /dev/null +++ b/test/lib/url-matcher.test.ts @@ -0,0 +1,27 @@ +import { matchURL } from '@/lib/funcs/matchers'; + +describe('matchURL', () => { + it('_', () => { + expect(matchURL('http://localhost')).not.toBeNull(); + }); + + it('_', () => { + expect(matchURL('https://foo.com')).not.toBeNull(); + }); + + it('_', () => { + expect(matchURL('http://foo.com')).not.toBeNull(); + }); + + it('_', () => { + expect(matchURL('htts://foo.com')).toBeNull(); + }); + + it('_', () => { + expect(matchURL('http//foo.com')).toBeNull(); + }); + + it('_', () => { + expect(matchURL('foo.com')).toBeNull(); + }); +}); From 0b38c29ab5615d963a424380993047d41a79027c Mon Sep 17 00:00:00 2001 From: AshGw Date: Sat, 17 Feb 2024 23:30:43 +0100 Subject: [PATCH 5/6] test: does testing for blog redirections --- src/lib/funcs/sequel-blogs-redirection.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/funcs/sequel-blogs-redirection.ts b/src/lib/funcs/sequel-blogs-redirection.ts index bb3be7eb..93775eda 100644 --- a/src/lib/funcs/sequel-blogs-redirection.ts +++ b/src/lib/funcs/sequel-blogs-redirection.ts @@ -21,4 +21,3 @@ export function whereToGo(href: string, where: Where): string { } return '#'; } - From 241994903ef692b2a5d559790cafc5594f3c383a Mon Sep 17 00:00:00 2001 From: AshGw Date: Sun, 18 Feb 2024 14:30:48 +0100 Subject: [PATCH 6/6] feat: cleanup --- package.json | 2 +- public/blogs/gsap-just-got-better.mdx | 127 +++++++++++++++++- ...jest-and-playwright-conflicts-solution.mdx | 13 ++ .../blogs/redis-instances-local-testing.mdx | 6 +- src/app/(pages)/x/page.tsx | 57 +------- src/app/components/blog-mentions/gsap.tsx | 48 +++++++ src/app/components/mdx/styled-mdx.tsx | 2 + 7 files changed, 195 insertions(+), 60 deletions(-) create mode 100644 public/blogs/jest-and-playwright-conflicts-solution.mdx create mode 100644 src/app/components/blog-mentions/gsap.tsx diff --git a/package.json b/package.json index 8feae7a8..a441db29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mysite", - "version": "1.0.1", + "version": "0.8.0", "private": true, "scripts": { "dev": "next dev", diff --git a/public/blogs/gsap-just-got-better.mdx b/public/blogs/gsap-just-got-better.mdx index 8c656ba9..cadcda42 100644 --- a/public/blogs/gsap-just-got-better.mdx +++ b/public/blogs/gsap-just-got-better.mdx @@ -6,8 +6,133 @@ isReleased: true isSequel: false lastModDate: 2024-01-19T09:15:00-0401 firstModDate: 2024-01-19T09:15:00-0401 -minutesToRead: 18 +minutesToRead: 3 tags: - 'react' - 'gsap' --- + +

The Update

+ +Over the past few years, I've primarily relied on Framer Motion for handling my animations, while incorporating GSAP from time to time, I found it to be somewhat verbose compared to **Framer**, especially in a **React** context. + +But, check this out, yesterday **GSAP** introduced the **``useGSAP()``** hook, an addition to easily integrate **GSAP** with **React**. + +Prior to this, animating elements involved wrapping them in a **``gsap.Context``** object, then, you had to use the same object's methods like **``.revert()``** or **``.kill()``** within **``useEffect()``** or **``useLayoutEffect()``** cleanup function to avoid memory leaks, depending if you're within an **SSR** context or not, along with a mandatory dependency array that you have to keep track of. + + To do something like + + +You'd probably need to + { + const container = useRef(null); + + useIsoLayoutEffect(() => { + const ctx = gsap.context(() => { + const tl = gsap.timeline({ repeat: -1, repeatDelay: 0.5 }); + + tl.to('.x', { + rotation: 360, + duration: 2, + borderRadius: 16, + translateX: -150, + ease: 'power1.inOut', + }); + tl.to('.x', { + rotation: -360, + duration: 2, + borderRadius: 0, + translateX: 150, + ease: 'power1.inOut', + }); + tl.to('.x', { + rotation: 360, + duration: 2, + borderRadius: 16, + translateX: 0, + ease: 'power1.inOut', + }); + }, container); + + return () => ctx.revert(); + }, []); + + return ( +
+
+
+
+
+
+
+ ); +}; +`} + language="tsx" + showLineNumbers={false} +/> +This new hook abstracts away these inconveniences, and obviates the need for explicit cleanup. + So the same Logic above can be written as + { + const container = useRef(null); + + useGSAP( + () => { + const tl = gsap.timeline({ repeat: -1, repeatDelay: 0.5 }); + tl.to('.x', { + rotation: 360, + duration: 2, + borderRadius: 16, + translateX: -150, + ease: 'power1.inOut', + }); + tl.to('.x', { + rotation: -360, + duration: 2, + borderRadius: 0, + translateX: 150, + ease: 'power1.inOut', + }); + tl.to('.x', { + rotation: 360, + duration: 2, + borderRadius: 16, + translateX: 0, + ease: 'power1.inOut', + }); + }, + { scope: container} + ); + return ( +
+
+
+
+
+
+
+ ); +}; +`} + language="tsx" + showLineNumbers={false} +/> +I like this update. I might start using **GSAP** even more now diff --git a/public/blogs/jest-and-playwright-conflicts-solution.mdx b/public/blogs/jest-and-playwright-conflicts-solution.mdx new file mode 100644 index 00000000..f1d99492 --- /dev/null +++ b/public/blogs/jest-and-playwright-conflicts-solution.mdx @@ -0,0 +1,13 @@ +--- +title: Jest and Playwright +seoTitle: How to actually run both Jest and Playwright in the same codebase at the same time +summary: How to actually run both test runners in the same codebase at the same time +isReleased: true +isSequel: false +lastModDate: 2023-09-01T09:15:00-0401 +firstModDate: 2023-09-01T09:15:00-0401 +minutesToRead: 18 +tags: + - 'jest' + - 'playwright' +--- \ No newline at end of file diff --git a/public/blogs/redis-instances-local-testing.mdx b/public/blogs/redis-instances-local-testing.mdx index ae813d04..b89532e5 100644 --- a/public/blogs/redis-instances-local-testing.mdx +++ b/public/blogs/redis-instances-local-testing.mdx @@ -31,7 +31,7 @@ tags: - \- Docker & Compose - Here's the basic API endpoints we want limited + These are the basic endpoints that are rate-limited - For the rate limiting logic, we'll use ``fastapi_limiter``. Though you can + For the rate limiting logic, I'll use ``fastapi_limiter``. Though you can roll out your own. Here's a state of the art, robust, elegant and 120% prod ready service @@ -177,7 +177,7 @@ protected-mode no more about all the config options here - To actually run it we need a Dockerfile: + To actually run it mfs need a Dockerfile: (null); - - useGSAP( - () => { - const tl = gsap.timeline(); - tl.to('.box', { - rotation: 360, - duration: 2, - repeat: -1, - ease: 'expo', - }); - }, - { scope: container } - ); - - return ( -
-
-
Hello
-
-
- - -
- ); -} - -function NextArticle() { - return ( - <> - - - ); -} +import LoadingScreen from '../blog/loading'; +export default LoadingScreen; diff --git a/src/app/components/blog-mentions/gsap.tsx b/src/app/components/blog-mentions/gsap.tsx new file mode 100644 index 00000000..b2617a36 --- /dev/null +++ b/src/app/components/blog-mentions/gsap.tsx @@ -0,0 +1,48 @@ +'use client'; +import React, { useRef } from 'react'; +import gsap from 'gsap'; +import { useGSAP } from '@gsap/react'; + +export const ThreeTrafficLightsMovingObjects: React.FC = () => { + const container = useRef(null); + + useGSAP( + () => { + const tl = gsap.timeline({ repeat: -1, repeatDelay: 0.5 }); + tl.to('.x', { + rotation: 360, + duration: 2, + borderRadius: 16, + translateX: -150, + ease: 'power1.inOut', + }); + tl.to('.x', { + rotation: -360, + duration: 2, + borderRadius: 0, + translateX: 150, + ease: 'power1.inOut', + }); + tl.to('.x', { + rotation: 360, + duration: 2, + borderRadius: 16, + translateX: 0, + ease: 'power1.inOut', + }); + }, + { scope: container, dependencies: [] } + ); + return ( +
+
+
+
+
+
+
+ ); +}; diff --git a/src/app/components/mdx/styled-mdx.tsx b/src/app/components/mdx/styled-mdx.tsx index 4ee31859..c08e17d3 100644 --- a/src/app/components/mdx/styled-mdx.tsx +++ b/src/app/components/mdx/styled-mdx.tsx @@ -14,6 +14,7 @@ import { YeetMe, FramerMotionFadeInComponent, } from '@/app/components/blog-mentions/framer-motion'; +import { ThreeTrafficLightsMovingObjects } from '@/app/components/blog-mentions/gsap'; export default function StyledMDX({ source }: { source: string }) { return ( @@ -43,4 +44,5 @@ function _StyledMDX({ components, ...props }: MDXRemoteProps) { const featuredComponents = { FramerMotionFadeInComponent: FramerMotionFadeInComponent, YeetMe: YeetMe, + TTLMO: ThreeTrafficLightsMovingObjects, };